routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
import { RuleOptions } from "./rule-options.js";
|
||||
import { ESLint, Linter, Rule } from "eslint";
|
||||
|
||||
//#region src/dts/configs.d.ts
|
||||
declare const configs: {
|
||||
/**
|
||||
* The default recommended config in Flat Config Format
|
||||
*/
|
||||
recommended: Linter.Config;
|
||||
/**
|
||||
* Enable all rules, in Flat Config Format
|
||||
*/
|
||||
all: Linter.Config;
|
||||
};
|
||||
type Configs = typeof configs;
|
||||
//#endregion
|
||||
//#region src/dts/rules.d.ts
|
||||
type RuleName<K extends string> = K extends `${string}/${infer Name}` ? RuleName<Name> : K;
|
||||
type Rules = Required<{ [K in keyof RuleOptions as RuleName<K>]: Rule.RuleModule }>;
|
||||
//#endregion
|
||||
//#region src/dts/index.d.ts
|
||||
declare const plugin: {
|
||||
rules: Rules;
|
||||
configs: ESLint.Plugin['configs'] & Configs;
|
||||
};
|
||||
//#endregion
|
||||
export { type Configs, type RuleOptions, type Rules, plugin as default };
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import { Linter } from "eslint";
|
||||
|
||||
//#region src/dts/rule-options.d.ts
|
||||
interface RuleOptions {
|
||||
/**
|
||||
* Enforce or ban the use of inline type-only markers for named imports.
|
||||
* @see https://github.com/9romise/eslint-plugin-import-lite/blob/main/src/rules/consistent-type-specifier-style/README.md
|
||||
*/
|
||||
'import-lite/consistent-type-specifier-style'?: Linter.RuleEntry<ImportLiteConsistentTypeSpecifierStyle>;
|
||||
/**
|
||||
* Ensure all exports appear after other statements.
|
||||
* @see https://github.com/9romise/eslint-plugin-import-lite/blob/main/src/rules/exports-last/README.md
|
||||
*/
|
||||
'import-lite/exports-last'?: Linter.RuleEntry<[]>;
|
||||
/**
|
||||
* Ensure all imports appear before other statements.
|
||||
* @see https://github.com/9romise/eslint-plugin-import-lite/blob/main/src/rules/first/README.md
|
||||
*/
|
||||
'import-lite/first'?: Linter.RuleEntry<ImportLiteFirst>;
|
||||
/**
|
||||
* Enforce a newline after import statements.
|
||||
* @see https://github.com/9romise/eslint-plugin-import-lite/blob/main/src/rules/newline-after-import/README.md
|
||||
*/
|
||||
'import-lite/newline-after-import'?: Linter.RuleEntry<ImportLiteNewlineAfterImport>;
|
||||
/**
|
||||
* Forbid default exports.
|
||||
* @see https://github.com/9romise/eslint-plugin-import-lite/blob/main/src/rules/no-default-export/README.md
|
||||
*/
|
||||
'import-lite/no-default-export'?: Linter.RuleEntry<[]>;
|
||||
/**
|
||||
* Forbid repeated import of the same module in multiple places.
|
||||
* @see https://github.com/9romise/eslint-plugin-import-lite/blob/main/src/rules/no-duplicates/README.md
|
||||
*/
|
||||
'import-lite/no-duplicates'?: Linter.RuleEntry<ImportLiteNoDuplicates>;
|
||||
/**
|
||||
* Forbid the use of mutable exports with `var` or `let`.
|
||||
* @see https://github.com/9romise/eslint-plugin-import-lite/blob/main/src/rules/no-mutable-exports/README.md
|
||||
*/
|
||||
'import-lite/no-mutable-exports'?: Linter.RuleEntry<[]>;
|
||||
/**
|
||||
* Forbid named default exports.
|
||||
* @see https://github.com/9romise/eslint-plugin-import-lite/blob/main/src/rules/no-named-default/README.md
|
||||
*/
|
||||
'import-lite/no-named-default'?: Linter.RuleEntry<[]>;
|
||||
/**
|
||||
* Prefer a default export if module exports a single name or multiple names.
|
||||
* @see https://github.com/9romise/eslint-plugin-import-lite/blob/main/src/rules/prefer-default-export/README.md
|
||||
*/
|
||||
'import-lite/prefer-default-export'?: Linter.RuleEntry<ImportLitePreferDefaultExport>;
|
||||
}
|
||||
/* ======= Declarations ======= */
|
||||
// ----- import-lite/consistent-type-specifier-style -----
|
||||
type ImportLiteConsistentTypeSpecifierStyle = [] | [("top-level" | "inline" | "prefer-top-level")]; // ----- import-lite/first -----
|
||||
type ImportLiteFirst = [] | [("absolute-first" | "disable-absolute-first")]; // ----- import-lite/newline-after-import -----
|
||||
type ImportLiteNewlineAfterImport = [] | [{
|
||||
count?: number;
|
||||
exactCount?: boolean;
|
||||
considerComments?: boolean;
|
||||
}]; // ----- import-lite/no-duplicates -----
|
||||
type ImportLiteNoDuplicates = [] | [{
|
||||
"prefer-inline"?: boolean;
|
||||
}]; // ----- import-lite/prefer-default-export -----
|
||||
type ImportLitePreferDefaultExport = [] | [{
|
||||
target?: ("single" | "any");
|
||||
}];
|
||||
//#endregion
|
||||
export { RuleOptions };
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { t as consistent_type_specifier_style_default } from "./rules/consistent-type-specifier-style.mjs";
|
||||
import { t as exports_last_default } from "./rules/exports-last.mjs";
|
||||
import { t as first_default } from "./rules/first.mjs";
|
||||
import { t as newline_after_import_default } from "./rules/newline-after-import.mjs";
|
||||
import { t as no_default_export_default } from "./rules/no-default-export.mjs";
|
||||
import { t as no_duplicates_default } from "./rules/no-duplicates.mjs";
|
||||
import { t as no_mutable_exports_default } from "./rules/no-mutable-exports.mjs";
|
||||
import { t as no_named_default_default } from "./rules/no-named-default.mjs";
|
||||
import { t as prefer_default_export_default } from "./rules/prefer-default-export.mjs";
|
||||
//#region src/rules/index.ts
|
||||
const rules = {
|
||||
"consistent-type-specifier-style": consistent_type_specifier_style_default,
|
||||
"exports-last": exports_last_default,
|
||||
"first": first_default,
|
||||
"newline-after-import": newline_after_import_default,
|
||||
"no-default-export": no_default_export_default,
|
||||
"no-duplicates": no_duplicates_default,
|
||||
"no-mutable-exports": no_mutable_exports_default,
|
||||
"no-named-default": no_named_default_default,
|
||||
"prefer-default-export": prefer_default_export_default
|
||||
};
|
||||
//#endregion
|
||||
//#region src/index.ts
|
||||
const pluginName = "import-lite";
|
||||
function generateConfig(name, filter = () => true) {
|
||||
const ruleMeta = Object.entries(rules).filter(([ruleName, rule]) => !rule.meta?.deprecated && filter(ruleName, rule));
|
||||
return {
|
||||
name: `${pluginName}/${name}`,
|
||||
plugins: { [pluginName]: {
|
||||
name: pluginName,
|
||||
rules
|
||||
} },
|
||||
rules: Object.fromEntries(ruleMeta.map(([ruleName]) => [`${pluginName}/${ruleName}`, "error"]))
|
||||
};
|
||||
}
|
||||
var src_default = {
|
||||
rules,
|
||||
configs: {
|
||||
recommended: generateConfig("recommended", (_, rule) => !!rule.meta?.docs?.recommended),
|
||||
all: generateConfig("all")
|
||||
}
|
||||
};
|
||||
//#endregion
|
||||
export { src_default as default, pluginName };
|
||||
Generated
Vendored
+101
@@ -0,0 +1,101 @@
|
||||
import { a as createRule, i as isCommaToken, r as getValue } from "../utils.mjs";
|
||||
//#region src/rules/consistent-type-specifier-style/consistent-type-specifier-style.ts
|
||||
function getImportText(node, sourceCode, specifiers) {
|
||||
const sourceString = sourceCode.getText(node.source);
|
||||
if (specifiers.length === 0) return "";
|
||||
return `import type {${specifiers.map((s) => {
|
||||
const importedName = getValue(s.imported);
|
||||
if (importedName === s.local.name) return importedName;
|
||||
return `${importedName} as ${s.local.name}`;
|
||||
}).join(", ")}} from ${sourceString};`;
|
||||
}
|
||||
function hasResolutionModeAttribute(node) {
|
||||
return node.attributes?.some((attr) => attr.key.type === "Literal" && attr.key.value === "resolution-mode");
|
||||
}
|
||||
var consistent_type_specifier_style_default = createRule({
|
||||
name: "consistent-type-specifier-style",
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: { description: "Enforce or ban the use of inline type-only markers for named imports." },
|
||||
fixable: "code",
|
||||
schema: [{
|
||||
type: "string",
|
||||
enum: [
|
||||
"top-level",
|
||||
"inline",
|
||||
"prefer-top-level"
|
||||
],
|
||||
default: "top-level"
|
||||
}],
|
||||
messages: {
|
||||
inline: "Prefer using inline {{kind}} specifiers instead of a top-level {{kind}}-only import.",
|
||||
topLevel: "Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers."
|
||||
}
|
||||
},
|
||||
defaultOptions: ["top-level"],
|
||||
create(context, [options]) {
|
||||
const { sourceCode } = context;
|
||||
if (options === "inline") return { ImportDeclaration(node) {
|
||||
if (node.importKind === "value" || node.importKind == null) return;
|
||||
if (hasResolutionModeAttribute(node)) return;
|
||||
if (node.specifiers.length === 0 || node.specifiers.length === 1 && (node.specifiers[0].type === "ImportDefaultSpecifier" || node.specifiers[0].type === "ImportNamespaceSpecifier")) return;
|
||||
context.report({
|
||||
node,
|
||||
messageId: "inline",
|
||||
data: { kind: node.importKind },
|
||||
fix(fixer) {
|
||||
const kindToken = sourceCode.getFirstToken(node, { skip: 1 });
|
||||
return [kindToken ? fixer.remove(kindToken) : [], node.specifiers.map((specifier) => fixer.insertTextBefore(specifier, `${node.importKind} `))].flat();
|
||||
}
|
||||
});
|
||||
} };
|
||||
return { ImportDeclaration(node) {
|
||||
if (node.importKind === "type" || node.specifiers.length === 0 || node.specifiers.length === 1 && (node.specifiers[0].type === "ImportDefaultSpecifier" || node.specifiers[0].type === "ImportNamespaceSpecifier")) return;
|
||||
const typeSpecifiers = [];
|
||||
const valueSpecifiers = [];
|
||||
let defaultSpecifier = null;
|
||||
for (const specifier of node.specifiers) {
|
||||
if (specifier.type === "ImportDefaultSpecifier") {
|
||||
defaultSpecifier = specifier;
|
||||
continue;
|
||||
}
|
||||
if (!("importKind" in specifier)) continue;
|
||||
if (specifier.importKind === "type") typeSpecifiers.push(specifier);
|
||||
else if (specifier.importKind === "value" || specifier.importKind == null) valueSpecifiers.push(specifier);
|
||||
}
|
||||
const typeImport = getImportText(node, sourceCode, typeSpecifiers);
|
||||
if (typeSpecifiers.length === node.specifiers.length) context.report({
|
||||
node,
|
||||
messageId: "topLevel",
|
||||
data: { kind: "type" },
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(node, typeImport);
|
||||
}
|
||||
});
|
||||
else if (options === "top-level") for (const specifier of typeSpecifiers) context.report({
|
||||
node: specifier,
|
||||
messageId: "topLevel",
|
||||
data: { kind: specifier.importKind },
|
||||
fix(fixer) {
|
||||
const fixes = [];
|
||||
if (valueSpecifiers.length > 0) {
|
||||
for (const specifier of typeSpecifiers) {
|
||||
const token = sourceCode.getTokenAfter(specifier);
|
||||
if (token && isCommaToken(token)) fixes.push(fixer.remove(token));
|
||||
fixes.push(fixer.remove(specifier));
|
||||
}
|
||||
const maybeComma = sourceCode.getTokenAfter(valueSpecifiers.at(-1));
|
||||
if (isCommaToken(maybeComma)) fixes.push(fixer.remove(maybeComma));
|
||||
} else if (defaultSpecifier) {
|
||||
const comma = sourceCode.getTokenAfter(defaultSpecifier, isCommaToken);
|
||||
const closingBrace = sourceCode.getTokenAfter(node.specifiers.at(-1), (token) => token.type === "Punctuator" && token.value === "}");
|
||||
fixes.push(fixer.removeRange([comma.range[0], closingBrace.range[1]]));
|
||||
}
|
||||
return [...fixes, fixer.insertTextAfter(node, `\n${typeImport}`)];
|
||||
}
|
||||
});
|
||||
} };
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
export { consistent_type_specifier_style_default as t };
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { a as createRule } from "../utils.mjs";
|
||||
//#region src/rules/exports-last/exports-last.ts
|
||||
function isNonExportStatement({ type }) {
|
||||
return type !== "ExportDefaultDeclaration" && type !== "ExportNamedDeclaration" && type !== "ExportAllDeclaration";
|
||||
}
|
||||
var exports_last_default = createRule({
|
||||
name: "exports-last",
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: { description: "Ensure all exports appear after other statements." },
|
||||
schema: [],
|
||||
messages: { end: "Export statements should appear at the end of the file" }
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
return { Program({ body }) {
|
||||
const lastNonExportStatementIndex = body.findLastIndex(isNonExportStatement);
|
||||
if (lastNonExportStatementIndex !== -1) {
|
||||
for (const node of body.slice(0, lastNonExportStatementIndex)) if (!isNonExportStatement(node)) context.report({
|
||||
node,
|
||||
messageId: "end"
|
||||
});
|
||||
}
|
||||
} };
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
export { exports_last_default as t };
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import { a as createRule } from "../utils.mjs";
|
||||
//#region src/rules/first/first.ts
|
||||
const RELATIVE_PATTERN = /^\./;
|
||||
const NON_WHITESPACE_PATTERN = /\S/;
|
||||
const LEADING_WHITESPACE_PATTERN = /^(\s+)/;
|
||||
function getImportValue(node) {
|
||||
return node.type === "ImportDeclaration" ? node.source.value : "moduleReference" in node && "expression" in node.moduleReference && "value" in node.moduleReference.expression && node.moduleReference.expression.value;
|
||||
}
|
||||
function isPossibleDirective(node) {
|
||||
return node.type === "ExpressionStatement" && node.expression.type === "Literal" && typeof node.expression.value === "string";
|
||||
}
|
||||
var first_default = createRule({
|
||||
name: "first",
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: { description: "Ensure all imports appear before other statements." },
|
||||
fixable: "code",
|
||||
schema: [{
|
||||
type: "string",
|
||||
enum: ["absolute-first", "disable-absolute-first"]
|
||||
}],
|
||||
messages: {
|
||||
absolute: "Absolute imports should come before relative imports.",
|
||||
order: "Import in body of module; reorder to top."
|
||||
}
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context, options) {
|
||||
return { Program(n) {
|
||||
const body = n.body;
|
||||
if (!body?.length) return;
|
||||
const absoluteFirst = options[0] === "absolute-first";
|
||||
const { sourceCode } = context;
|
||||
const originSourceCode = sourceCode.getText();
|
||||
let nonImportCount = 0;
|
||||
let anyExpressions = false;
|
||||
let anyRelative = false;
|
||||
let lastLegalImp = null;
|
||||
const errorInfos = [];
|
||||
let shouldSort = true;
|
||||
let lastSortNodesIndex = 0;
|
||||
for (const [index, node] of body.entries()) {
|
||||
if (!anyExpressions && isPossibleDirective(node)) continue;
|
||||
anyExpressions = true;
|
||||
if (node.type === "ImportDeclaration" || node.type === "TSImportEqualsDeclaration") {
|
||||
if (absoluteFirst) {
|
||||
const importValue = getImportValue(node);
|
||||
if (typeof importValue === "string" && RELATIVE_PATTERN.test(importValue)) anyRelative = true;
|
||||
else if (anyRelative) context.report({
|
||||
node: node.type === "ImportDeclaration" ? node.source : node.moduleReference,
|
||||
messageId: "absolute"
|
||||
});
|
||||
}
|
||||
if (nonImportCount > 0) {
|
||||
/** @see https://eslint.org/docs/next/use/migrate-to-9.0.0#-removed-multiple-context-methods */
|
||||
for (const variable of sourceCode.getDeclaredVariables(node)) {
|
||||
if (!shouldSort) break;
|
||||
for (const reference of variable.references) if (reference.identifier.range[0] < node.range[1]) {
|
||||
shouldSort = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (shouldSort) lastSortNodesIndex = errorInfos.length;
|
||||
errorInfos.push({
|
||||
node,
|
||||
range: [body[index - 1].range[1], node.range[1]]
|
||||
});
|
||||
} else lastLegalImp = node;
|
||||
} else nonImportCount++;
|
||||
}
|
||||
if (errorInfos.length === 0) return;
|
||||
for (const [index, { node }] of errorInfos.entries()) {
|
||||
let fix;
|
||||
if (index < lastSortNodesIndex) fix = (fixer) => fixer.insertTextAfter(node, "");
|
||||
else if (index === lastSortNodesIndex) {
|
||||
const sortNodes = errorInfos.slice(0, lastSortNodesIndex + 1);
|
||||
fix = (fixer) => {
|
||||
const removeFixers = sortNodes.map(({ range }) => fixer.removeRange(range));
|
||||
const range = [0, removeFixers.at(-1).range[1]];
|
||||
let insertSourceCode = sortNodes.map(({ range }) => {
|
||||
const nodeSourceCode = originSourceCode.slice(...range);
|
||||
if (NON_WHITESPACE_PATTERN.test(nodeSourceCode[0])) return `\n${nodeSourceCode}`;
|
||||
return nodeSourceCode;
|
||||
}).join("");
|
||||
let replaceSourceCode = "";
|
||||
if (!lastLegalImp) insertSourceCode = insertSourceCode.trim() + insertSourceCode.match(LEADING_WHITESPACE_PATTERN)[0];
|
||||
const fixers = [lastLegalImp ? fixer.insertTextAfter(lastLegalImp, insertSourceCode) : fixer.insertTextBefore(body[0], insertSourceCode), ...removeFixers];
|
||||
for (const [i, computedFixer] of fixers.entries()) replaceSourceCode += originSourceCode.slice(fixers[i - 1] ? fixers[i - 1].range[1] : 0, computedFixer.range[0]) + computedFixer.text;
|
||||
return fixer.replaceTextRange(range, replaceSourceCode);
|
||||
};
|
||||
}
|
||||
context.report({
|
||||
node,
|
||||
messageId: "order",
|
||||
fix
|
||||
});
|
||||
}
|
||||
} };
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
export { first_default as t };
|
||||
Generated
Vendored
+168
@@ -0,0 +1,168 @@
|
||||
import { a as createRule } from "../utils.mjs";
|
||||
//#region src/rules/newline-after-import/newline-after-import.ts
|
||||
function isStaticRequire(node) {
|
||||
return node && node.callee && node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length === 1 && node.arguments[0].type === "Literal" && typeof node.arguments[0].value === "string";
|
||||
}
|
||||
function containsNodeOrEqual(outerNode, innerNode) {
|
||||
return outerNode.range[0] <= innerNode.range[0] && outerNode.range[1] >= innerNode.range[1];
|
||||
}
|
||||
function getScopeBody(scope) {
|
||||
if (scope.block.type === "SwitchStatement") {
|
||||
console.log("SwitchStatement scopes not supported");
|
||||
return [];
|
||||
}
|
||||
const body = "body" in scope.block ? scope.block.body : null;
|
||||
if (body && "type" in body && body.type === "BlockStatement") return body.body;
|
||||
return Array.isArray(body) ? body : [];
|
||||
}
|
||||
function findNodeIndexInScopeBody(body, nodeToFind) {
|
||||
return body.findIndex((node) => containsNodeOrEqual(node, nodeToFind));
|
||||
}
|
||||
function getLineDifference(node, nextNode) {
|
||||
return nextNode.loc.start.line - node.loc.end.line;
|
||||
}
|
||||
function isClassWithDecorator(node) {
|
||||
return node.type === "ClassDeclaration" && !!node.decorators?.length;
|
||||
}
|
||||
function isExportDefaultClass(node) {
|
||||
return node.type === "ExportDefaultDeclaration" && node.declaration.type === "ClassDeclaration";
|
||||
}
|
||||
function isExportNameClass(node) {
|
||||
return node.type === "ExportNamedDeclaration" && node.declaration?.type === "ClassDeclaration";
|
||||
}
|
||||
var newline_after_import_default = createRule({
|
||||
name: "newline-after-import",
|
||||
meta: {
|
||||
type: "layout",
|
||||
docs: { description: "Enforce a newline after import statements." },
|
||||
fixable: "whitespace",
|
||||
schema: [{
|
||||
type: "object",
|
||||
properties: {
|
||||
count: {
|
||||
type: "integer",
|
||||
minimum: 1
|
||||
},
|
||||
exactCount: { type: "boolean" },
|
||||
considerComments: { type: "boolean" }
|
||||
},
|
||||
additionalProperties: false
|
||||
}],
|
||||
messages: { newline: "Expected {{count}} empty line{{lineSuffix}} after {{type}} statement not followed by another {{type}}." }
|
||||
},
|
||||
defaultOptions: [{
|
||||
count: 1,
|
||||
exactCount: false,
|
||||
considerComments: false
|
||||
}],
|
||||
create(context, [options]) {
|
||||
const { count = 1, exactCount = false, considerComments = false } = options || {};
|
||||
let level = 0;
|
||||
const requireCalls = [];
|
||||
function checkForNewLine(node, nextNode, type) {
|
||||
if (isExportDefaultClass(nextNode) || isExportNameClass(nextNode)) {
|
||||
const classNode = nextNode.declaration;
|
||||
if (isClassWithDecorator(classNode)) nextNode = classNode.decorators[0];
|
||||
} else if (isClassWithDecorator(nextNode)) nextNode = nextNode.decorators[0];
|
||||
const lineDifference = getLineDifference(node, nextNode);
|
||||
const EXPECTED_LINE_DIFFERENCE = count + 1;
|
||||
if (lineDifference < EXPECTED_LINE_DIFFERENCE || exactCount && lineDifference !== EXPECTED_LINE_DIFFERENCE) {
|
||||
let column = node.loc.start.column;
|
||||
if (node.loc.start.line !== node.loc.end.line) column = 0;
|
||||
context.report({
|
||||
loc: {
|
||||
line: node.loc.end.line,
|
||||
column
|
||||
},
|
||||
messageId: "newline",
|
||||
data: {
|
||||
count,
|
||||
lineSuffix: count > 1 ? "s" : "",
|
||||
type
|
||||
},
|
||||
fix: exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference ? void 0 : (fixer) => fixer.insertTextAfter(node, "\n".repeat(EXPECTED_LINE_DIFFERENCE - lineDifference))
|
||||
});
|
||||
}
|
||||
}
|
||||
function commentAfterImport(node, nextComment, type) {
|
||||
const lineDifference = getLineDifference(node, nextComment);
|
||||
const EXPECTED_LINE_DIFFERENCE = count + 1;
|
||||
if (lineDifference < EXPECTED_LINE_DIFFERENCE) {
|
||||
let column = node.loc.start.column;
|
||||
if (node.loc.start.line !== node.loc.end.line) column = 0;
|
||||
context.report({
|
||||
loc: {
|
||||
line: node.loc.end.line,
|
||||
column
|
||||
},
|
||||
messageId: "newline",
|
||||
data: {
|
||||
count,
|
||||
lineSuffix: count > 1 ? "s" : "",
|
||||
type
|
||||
},
|
||||
fix: exactCount && EXPECTED_LINE_DIFFERENCE < lineDifference ? void 0 : (fixer) => fixer.insertTextAfter(node, "\n".repeat(EXPECTED_LINE_DIFFERENCE - lineDifference))
|
||||
});
|
||||
}
|
||||
}
|
||||
function incrementLevel() {
|
||||
level++;
|
||||
}
|
||||
function decrementLevel() {
|
||||
level--;
|
||||
}
|
||||
function checkImport(node) {
|
||||
const { parent } = node;
|
||||
if (!parent || !("body" in parent) || !parent.body) return;
|
||||
const root = parent;
|
||||
const nodePosition = root.body.indexOf(node);
|
||||
const nextNode = root.body[nodePosition + 1];
|
||||
const endLine = node.loc.end.line;
|
||||
let nextComment;
|
||||
if (root.comments !== void 0 && considerComments) nextComment = root.comments.find((o) => o.loc.start.line >= endLine && o.loc.start.line <= endLine + count + 1);
|
||||
if (node.type === "TSImportEqualsDeclaration" && node.isExport) return;
|
||||
if (nextComment) commentAfterImport(node, nextComment, "import");
|
||||
else if (nextNode && nextNode.type !== "ImportDeclaration" && (nextNode.type !== "TSImportEqualsDeclaration" || nextNode.isExport)) checkForNewLine(node, nextNode, "import");
|
||||
}
|
||||
return {
|
||||
"ImportDeclaration": checkImport,
|
||||
"TSImportEqualsDeclaration": checkImport,
|
||||
CallExpression(node) {
|
||||
if (isStaticRequire(node) && level === 0) requireCalls.push(node);
|
||||
},
|
||||
"Program:exit": function(node) {
|
||||
const scopeBody = getScopeBody(context.sourceCode.getScope(node));
|
||||
for (const [index, node] of requireCalls.entries()) {
|
||||
const nodePosition = findNodeIndexInScopeBody(scopeBody, node);
|
||||
const statementWithRequireCall = scopeBody[nodePosition];
|
||||
const nextStatement = scopeBody[nodePosition + 1];
|
||||
const nextRequireCall = requireCalls[index + 1];
|
||||
if (nextRequireCall && containsNodeOrEqual(statementWithRequireCall, nextRequireCall)) continue;
|
||||
if (nextStatement && (!nextRequireCall || !containsNodeOrEqual(nextStatement, nextRequireCall))) {
|
||||
let nextComment;
|
||||
if ("comments" in statementWithRequireCall.parent && statementWithRequireCall.parent.comments !== void 0 && considerComments) {
|
||||
const endLine = node.loc.end.line;
|
||||
nextComment = statementWithRequireCall.parent.comments.find((o) => o.loc.start.line >= endLine && o.loc.start.line <= endLine + count + 1);
|
||||
}
|
||||
if (nextComment && nextComment !== void 0) commentAfterImport(statementWithRequireCall, nextComment, "require");
|
||||
else checkForNewLine(statementWithRequireCall, nextStatement, "require");
|
||||
}
|
||||
}
|
||||
},
|
||||
"FunctionDeclaration": incrementLevel,
|
||||
"FunctionExpression": incrementLevel,
|
||||
"ArrowFunctionExpression": incrementLevel,
|
||||
"BlockStatement": incrementLevel,
|
||||
"ObjectExpression": incrementLevel,
|
||||
"Decorator": incrementLevel,
|
||||
"FunctionDeclaration:exit": decrementLevel,
|
||||
"FunctionExpression:exit": decrementLevel,
|
||||
"ArrowFunctionExpression:exit": decrementLevel,
|
||||
"BlockStatement:exit": decrementLevel,
|
||||
"ObjectExpression:exit": decrementLevel,
|
||||
"Decorator:exit": decrementLevel
|
||||
};
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
export { newline_after_import_default as t };
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { a as createRule, n as sourceType, r as getValue } from "../utils.mjs";
|
||||
//#region src/rules/no-default-export/no-default-export.ts
|
||||
var no_default_export_default = createRule({
|
||||
name: "no-default-export",
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: { description: "Forbid default exports." },
|
||||
schema: [],
|
||||
messages: {
|
||||
preferNamed: "Prefer named exports.",
|
||||
noAliasDefault: "Do not alias `{{local}}` as `default`. Just export `{{local}}` itself instead."
|
||||
}
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
if (sourceType(context) !== "module") return {};
|
||||
const { sourceCode } = context;
|
||||
return {
|
||||
ExportDefaultDeclaration(node) {
|
||||
const { loc } = sourceCode.getFirstTokens(node)[1] || {};
|
||||
context.report({
|
||||
node,
|
||||
messageId: "preferNamed",
|
||||
loc
|
||||
});
|
||||
},
|
||||
ExportNamedDeclaration(node) {
|
||||
for (const specifier of node.specifiers.filter((specifier) => getValue(specifier.exported) === "default")) {
|
||||
const { loc } = sourceCode.getFirstTokens(node)[1] || {};
|
||||
if (specifier.type === "ExportDefaultSpecifier") context.report({
|
||||
node,
|
||||
messageId: "preferNamed",
|
||||
loc
|
||||
});
|
||||
else if (specifier.type === "ExportSpecifier") context.report({
|
||||
node,
|
||||
messageId: "noAliasDefault",
|
||||
data: { local: getValue(specifier.local) },
|
||||
loc
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
export { no_default_export_default as t };
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
import { a as createRule, t as resolve } from "../utils.mjs";
|
||||
//#region src/rules/no-duplicates/no-duplicates.ts
|
||||
const LEADING_WHITESPACE_PATTERN = /^(\s*)/;
|
||||
function checkImports(imported, context) {
|
||||
imported.forEach((nodes, module) => {
|
||||
if (nodes.length <= 1) return;
|
||||
for (let i = 0, len = nodes.length; i < len; i++) {
|
||||
const node = nodes[i];
|
||||
context.report({
|
||||
node: node.source,
|
||||
messageId: "duplicate",
|
||||
data: { module },
|
||||
fix: i === 0 ? getFix(nodes, context.sourceCode, context) : null
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function getSpecifiersByKind(node) {
|
||||
const typeSpecs = [];
|
||||
const valueSpecs = [];
|
||||
for (const spec of node.specifiers) {
|
||||
if (spec.type !== "ImportSpecifier") continue;
|
||||
const name = spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value;
|
||||
const localName = spec.local.name;
|
||||
("importKind" in spec && spec.importKind === "type" ? typeSpecs : valueSpecs).push({
|
||||
name,
|
||||
localName
|
||||
});
|
||||
}
|
||||
return {
|
||||
typeSpecs,
|
||||
valueSpecs
|
||||
};
|
||||
}
|
||||
function formatSpecifier(s) {
|
||||
return s.name !== s.localName ? `${s.name} as ${s.localName}` : s.name;
|
||||
}
|
||||
function getFix(nodes, sourceCode, context) {
|
||||
const first = nodes[0];
|
||||
const isTypeOnlyImport = first.importKind === "type";
|
||||
if (hasProblematicComments(first, sourceCode) || hasNamespace(first)) return null;
|
||||
const defaultImportNames = new Set(nodes.flatMap((x) => getDefaultImportName(x) || []));
|
||||
if (defaultImportNames.size > 1) return null;
|
||||
const restWithoutCommentsAndNamespaces = nodes.slice(1).filter((node) => !hasProblematicComments(node, sourceCode) && !hasNamespace(node));
|
||||
const restWithoutCommentsAndNamespacesHasSpecifiers = restWithoutCommentsAndNamespaces.map(hasSpecifiers);
|
||||
const specifiers = restWithoutCommentsAndNamespaces.reduce((acc, node, nodeIndex) => {
|
||||
const tokens = sourceCode.getTokens(node);
|
||||
const openBrace = tokens.find((token) => isPunctuator(token, "{"));
|
||||
const closeBrace = tokens.find((token) => isPunctuator(token, "}"));
|
||||
if (openBrace == null || closeBrace == null) return acc;
|
||||
const entry = {
|
||||
importNode: node,
|
||||
identifiers: sourceCode.text.slice(openBrace.range[1], closeBrace.range[0]).split(","),
|
||||
isEmpty: !restWithoutCommentsAndNamespacesHasSpecifiers[nodeIndex]
|
||||
};
|
||||
if (isTypeOnlyImport && node.importKind !== "type") {
|
||||
const { typeSpecs, valueSpecs } = getSpecifiersByKind(node);
|
||||
if (typeSpecs.length > 0 && valueSpecs.length > 0) {
|
||||
entry.typeSpecs = typeSpecs;
|
||||
entry.valueSpecs = valueSpecs;
|
||||
}
|
||||
}
|
||||
acc.push(entry);
|
||||
return acc;
|
||||
}, []);
|
||||
const unnecessaryImports = restWithoutCommentsAndNamespaces.filter((node, nodeIndex) => !restWithoutCommentsAndNamespacesHasSpecifiers[nodeIndex] && !specifiers.some((specifier) => specifier.importNode === node));
|
||||
const shouldAddSpecifiers = specifiers.length > 0;
|
||||
const shouldRemoveUnnecessary = unnecessaryImports.length > 0;
|
||||
const shouldAddDefault = getDefaultImportName(first) == null && defaultImportNames.size === 1;
|
||||
if (!shouldAddSpecifiers && !shouldRemoveUnnecessary && !shouldAddDefault) return null;
|
||||
const preferInline = context.options[0] && context.options[0]["prefer-inline"];
|
||||
return (fixer) => {
|
||||
const tokens = sourceCode.getTokens(first);
|
||||
const openBrace = tokens.find((token) => isPunctuator(token, "{"));
|
||||
const closeBrace = tokens.find((token) => isPunctuator(token, "}"));
|
||||
const firstToken = sourceCode.getFirstToken(first);
|
||||
const [defaultImportName] = defaultImportNames;
|
||||
const firstHasTrailingComma = closeBrace != null && isPunctuator(sourceCode.getTokenBefore(closeBrace), ",");
|
||||
const firstIsEmpty = !hasSpecifiers(first);
|
||||
const firstExistingIdentifiers = firstIsEmpty ? /* @__PURE__ */ new Set() : new Set(sourceCode.text.slice(openBrace.range[1], closeBrace.range[0]).split(",").map((x) => x.split(" as ")[0].trim()));
|
||||
const [specifiersText] = specifiers.reduce(([result, needsComma, existingIdentifiers], specifier) => {
|
||||
if (specifier.typeSpecs) {
|
||||
const newSpecs = specifier.typeSpecs.filter((ts) => !existingIdentifiers.has(ts.name));
|
||||
if (newSpecs.length === 0) return [
|
||||
result,
|
||||
needsComma,
|
||||
existingIdentifiers
|
||||
];
|
||||
const text = newSpecs.map(formatSpecifier).join(", ");
|
||||
const updatedSet = new Set(existingIdentifiers);
|
||||
newSpecs.forEach((ts) => updatedSet.add(ts.name));
|
||||
return [
|
||||
needsComma ? `${result}, ${text}` : `${result}${text}`,
|
||||
true,
|
||||
updatedSet
|
||||
];
|
||||
}
|
||||
const isTypeSpecifier = "importNode" in specifier && specifier.importNode.importKind === "type";
|
||||
const [specifierText, updatedExistingIdentifiers] = specifier.identifiers.reduce(([text, set], cur) => {
|
||||
const trimmed = cur.trim();
|
||||
if (trimmed.length === 0 || existingIdentifiers.has(trimmed)) return [text, set];
|
||||
const curWithType = preferInline && isTypeSpecifier ? cur.replace(LEADING_WHITESPACE_PATTERN, "$1type ") : cur;
|
||||
return [text.length > 0 ? `${text},${curWithType}` : curWithType, set.add(trimmed)];
|
||||
}, ["", existingIdentifiers]);
|
||||
return [
|
||||
needsComma && !specifier.isEmpty && specifierText.length > 0 ? `${result},${specifierText}` : `${result}${specifierText}`,
|
||||
specifier.isEmpty ? needsComma : true,
|
||||
updatedExistingIdentifiers
|
||||
];
|
||||
}, [
|
||||
"",
|
||||
!firstHasTrailingComma && !firstIsEmpty,
|
||||
firstExistingIdentifiers
|
||||
]);
|
||||
const fixes = [];
|
||||
if (shouldAddSpecifiers && preferInline && first.importKind === "type") {
|
||||
const typeIdentifierToken = tokens.find((token) => token.type === "Identifier" && token.value === "type");
|
||||
if (typeIdentifierToken) fixes.push(fixer.removeRange([typeIdentifierToken.range[0], typeIdentifierToken.range[1] + 1]));
|
||||
for (const identifier of tokens.filter((token) => firstExistingIdentifiers.has(token.value))) fixes.push(fixer.replaceTextRange([identifier.range[0], identifier.range[1]], `type ${identifier.value}`));
|
||||
}
|
||||
if (openBrace == null && shouldAddSpecifiers && shouldAddDefault) fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName}, {${specifiersText}} from`));
|
||||
else if (openBrace == null && !shouldAddSpecifiers && shouldAddDefault) fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName} from`));
|
||||
else if (openBrace != null && closeBrace != null && shouldAddDefault) {
|
||||
fixes.push(fixer.insertTextAfter(firstToken, ` ${defaultImportName},`));
|
||||
if (shouldAddSpecifiers) fixes.push(fixer.insertTextBefore(closeBrace, specifiersText));
|
||||
} else if (openBrace == null && shouldAddSpecifiers && !shouldAddDefault) if (first.specifiers.length === 0) fixes.push(fixer.insertTextAfter(firstToken, ` {${specifiersText}} from`));
|
||||
else fixes.push(fixer.insertTextAfter(first.specifiers[0], `, {${specifiersText}}`));
|
||||
else if (openBrace != null && closeBrace != null && !shouldAddDefault) {
|
||||
const tokenBefore = sourceCode.getTokenBefore(closeBrace);
|
||||
fixes.push(fixer.insertTextAfter(tokenBefore, specifiersText));
|
||||
}
|
||||
for (const specifier of specifiers) {
|
||||
const importNode = specifier.importNode;
|
||||
if (specifier.valueSpecs) {
|
||||
const nodeTokens = sourceCode.getTokens(importNode);
|
||||
const nodeOpenBrace = nodeTokens.find((token) => isPunctuator(token, "{"));
|
||||
const nodeCloseBrace = nodeTokens.find((token) => isPunctuator(token, "}"));
|
||||
if (nodeOpenBrace && nodeCloseBrace) fixes.push(fixer.replaceTextRange([nodeOpenBrace.range[1], nodeCloseBrace.range[0]], ` ${specifier.valueSpecs.map(formatSpecifier).join(", ")} `));
|
||||
continue;
|
||||
}
|
||||
fixes.push(fixer.remove(importNode));
|
||||
const charAfterImportRange = [importNode.range[1], importNode.range[1] + 1];
|
||||
if (sourceCode.text.slice(charAfterImportRange[0], charAfterImportRange[1]) === "\n") fixes.push(fixer.removeRange(charAfterImportRange));
|
||||
}
|
||||
for (const node of unnecessaryImports) {
|
||||
fixes.push(fixer.remove(node));
|
||||
const charAfterImportRange = [node.range[1], node.range[1] + 1];
|
||||
if (sourceCode.text.slice(charAfterImportRange[0], charAfterImportRange[1]) === "\n") fixes.push(fixer.removeRange(charAfterImportRange));
|
||||
}
|
||||
return fixes;
|
||||
};
|
||||
}
|
||||
function isPunctuator(node, value) {
|
||||
return node.type === "Punctuator" && node.value === value;
|
||||
}
|
||||
function getDefaultImportName(node) {
|
||||
return node.specifiers.find((specifier) => specifier.type === "ImportDefaultSpecifier")?.local.name;
|
||||
}
|
||||
function hasNamespace(node) {
|
||||
return node.specifiers.some((specifier) => specifier.type === "ImportNamespaceSpecifier");
|
||||
}
|
||||
function hasSpecifiers(node) {
|
||||
return node.specifiers.some((specifier) => specifier.type === "ImportSpecifier");
|
||||
}
|
||||
function hasProblematicComments(node, sourceCode) {
|
||||
return hasCommentBefore(node, sourceCode) || hasCommentAfter(node, sourceCode) || hasCommentInsideNonSpecifiers(node, sourceCode);
|
||||
}
|
||||
function hasCommentBefore(node, sourceCode) {
|
||||
return sourceCode.getCommentsBefore(node).some((comment) => comment.loc.end.line >= node.loc.start.line - 1);
|
||||
}
|
||||
function hasCommentAfter(node, sourceCode) {
|
||||
return sourceCode.getCommentsAfter(node).some((comment) => comment.loc.start.line === node.loc.end.line);
|
||||
}
|
||||
function hasCommentInsideNonSpecifiers(node, sourceCode) {
|
||||
const tokens = sourceCode.getTokens(node);
|
||||
const openBraceIndex = tokens.findIndex((token) => isPunctuator(token, "{"));
|
||||
const closeBraceIndex = tokens.findIndex((token) => isPunctuator(token, "}"));
|
||||
return (openBraceIndex !== -1 && closeBraceIndex !== -1 ? [...tokens.slice(1, openBraceIndex + 1), ...tokens.slice(closeBraceIndex + 1)] : tokens.slice(1)).some((token) => sourceCode.getCommentsBefore(token).length > 0);
|
||||
}
|
||||
var no_duplicates_default = createRule({
|
||||
name: "no-duplicates",
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
recommended: true,
|
||||
description: "Forbid repeated import of the same module in multiple places."
|
||||
},
|
||||
fixable: "code",
|
||||
schema: [{
|
||||
type: "object",
|
||||
properties: { "prefer-inline": { type: "boolean" } },
|
||||
additionalProperties: false
|
||||
}],
|
||||
messages: { duplicate: "'{{module}}' imported multiple times." }
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
const preferInline = context.options[0]?.["prefer-inline"];
|
||||
const moduleMaps = /* @__PURE__ */ new Map();
|
||||
function getImportMap(n) {
|
||||
const parent = n.parent;
|
||||
let map;
|
||||
if (moduleMaps.has(parent)) map = moduleMaps.get(parent);
|
||||
else {
|
||||
map = {
|
||||
imported: /* @__PURE__ */ new Map(),
|
||||
nsImported: /* @__PURE__ */ new Map(),
|
||||
defaultTypesImported: /* @__PURE__ */ new Map(),
|
||||
namespaceTypesImported: /* @__PURE__ */ new Map(),
|
||||
namedTypesImported: /* @__PURE__ */ new Map()
|
||||
};
|
||||
moduleMaps.set(parent, map);
|
||||
}
|
||||
if (n.importKind === "type") {
|
||||
if (n.specifiers.length > 0 && n.specifiers[0].type === "ImportDefaultSpecifier") return map.defaultTypesImported;
|
||||
if (n.specifiers.length > 0 && n.specifiers[0].type === "ImportNamespaceSpecifier") return map.namespaceTypesImported;
|
||||
if (!preferInline) return map.namedTypesImported;
|
||||
}
|
||||
if (!preferInline && n.specifiers.some((spec) => "importKind" in spec && spec.importKind === "type")) return map.namedTypesImported;
|
||||
return hasNamespace(n) ? map.nsImported : map.imported;
|
||||
}
|
||||
return {
|
||||
ImportDeclaration(n) {
|
||||
const resolvedPath = resolve(n.source.value);
|
||||
const importMap = getImportMap(n);
|
||||
if (importMap.has(resolvedPath)) importMap.get(resolvedPath).push(n);
|
||||
else importMap.set(resolvedPath, [n]);
|
||||
},
|
||||
"Program:exit": function() {
|
||||
for (const map of moduleMaps.values()) {
|
||||
checkImports(map.imported, context);
|
||||
checkImports(map.nsImported, context);
|
||||
checkImports(map.defaultTypesImported, context);
|
||||
checkImports(map.namedTypesImported, context);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
export { no_duplicates_default as t };
|
||||
Generated
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
import { a as createRule } from "../utils.mjs";
|
||||
//#region src/rules/no-mutable-exports/no-mutable-exports.ts
|
||||
var no_mutable_exports_default = createRule({
|
||||
name: "no-mutable-exports",
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: { description: "Forbid the use of mutable exports with `var` or `let`." },
|
||||
schema: [],
|
||||
messages: { noMutable: "Exporting mutable '{{kind}}' binding, use 'const' instead." }
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
function checkDeclaration(node) {
|
||||
if ("kind" in node && (node.kind === "var" || node.kind === "let")) context.report({
|
||||
node,
|
||||
messageId: "noMutable",
|
||||
data: { kind: node.kind }
|
||||
});
|
||||
}
|
||||
function checkDeclarationsInScope({ variables }, name) {
|
||||
for (const variable of variables) if (variable.name === name) {
|
||||
for (const def of variable.defs) if (def.type === "Variable" && def.parent) checkDeclaration(def.parent);
|
||||
}
|
||||
}
|
||||
return {
|
||||
ExportDefaultDeclaration(node) {
|
||||
const scope = context.sourceCode.getScope(node);
|
||||
if ("name" in node.declaration) checkDeclarationsInScope(scope, node.declaration.name);
|
||||
},
|
||||
ExportNamedDeclaration(node) {
|
||||
const scope = context.sourceCode.getScope(node);
|
||||
if (node.declaration) checkDeclaration(node.declaration);
|
||||
else if (!node.source) for (const specifier of node.specifiers) checkDeclarationsInScope(scope, specifier.local.name);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
export { no_mutable_exports_default as t };
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { a as createRule, r as getValue } from "../utils.mjs";
|
||||
//#region src/rules/no-named-default/no-named-default.ts
|
||||
var no_named_default_default = createRule({
|
||||
name: "no-named-default",
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: { description: "Forbid named default exports." },
|
||||
schema: [],
|
||||
messages: { default: `Use default import syntax to import '{{importName}}'.` }
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
return { ImportDeclaration(node) {
|
||||
for (const im of node.specifiers) {
|
||||
if ("importKind" in im && im.importKind === "type") continue;
|
||||
if (im.type === "ImportSpecifier" && getValue(im.imported) === "default") context.report({
|
||||
node: im.local,
|
||||
messageId: "default",
|
||||
data: { importName: im.local.name }
|
||||
});
|
||||
}
|
||||
} };
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
export { no_named_default_default as t };
|
||||
Generated
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
import { a as createRule, r as getValue } from "../utils.mjs";
|
||||
//#region src/rules/prefer-default-export/prefer-default-export.ts
|
||||
var prefer_default_export_default = createRule({
|
||||
name: "prefer-default-export",
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: { description: "Prefer a default export if module exports a single name or multiple names." },
|
||||
schema: [{
|
||||
type: "object",
|
||||
properties: { target: {
|
||||
type: "string",
|
||||
enum: ["single", "any"],
|
||||
default: "single"
|
||||
} },
|
||||
additionalProperties: false
|
||||
}],
|
||||
messages: {
|
||||
single: "Prefer default export on a file with single export.",
|
||||
any: "Prefer default export to be present on every file that has export."
|
||||
}
|
||||
},
|
||||
defaultOptions: [{ target: "single" }],
|
||||
create(context, [options]) {
|
||||
let specifierExportCount = 0;
|
||||
let hasDefaultExport = false;
|
||||
let hasStarExport = false;
|
||||
let hasTypeExport = false;
|
||||
let namedExportNode;
|
||||
const { target } = options;
|
||||
function captureDeclaration(identifierOrPattern) {
|
||||
if (identifierOrPattern?.type === "ObjectPattern") for (const property of identifierOrPattern.properties) captureDeclaration(property.value);
|
||||
else if (identifierOrPattern?.type === "ArrayPattern") for (const el of identifierOrPattern.elements) captureDeclaration(el);
|
||||
else specifierExportCount++;
|
||||
}
|
||||
return {
|
||||
ExportDefaultSpecifier() {
|
||||
hasDefaultExport = true;
|
||||
},
|
||||
ExportSpecifier(node) {
|
||||
if (getValue(node.exported) === "default") hasDefaultExport = true;
|
||||
else {
|
||||
specifierExportCount++;
|
||||
namedExportNode = node;
|
||||
}
|
||||
},
|
||||
ExportNamedDeclaration(node) {
|
||||
if (!node.declaration) return;
|
||||
const { type } = node.declaration;
|
||||
if (type === "TSTypeAliasDeclaration" || type === "TSInterfaceDeclaration") {
|
||||
specifierExportCount++;
|
||||
hasTypeExport = true;
|
||||
return;
|
||||
}
|
||||
if ("declarations" in node.declaration && node.declaration.declarations) for (const declaration of node.declaration.declarations) captureDeclaration(declaration.id);
|
||||
else specifierExportCount++;
|
||||
namedExportNode = node;
|
||||
},
|
||||
ExportDefaultDeclaration() {
|
||||
hasDefaultExport = true;
|
||||
},
|
||||
ExportAllDeclaration() {
|
||||
hasStarExport = true;
|
||||
},
|
||||
"Program:exit": function() {
|
||||
if (hasDefaultExport || hasStarExport || hasTypeExport) return;
|
||||
if (target === "single" && specifierExportCount === 1) context.report({
|
||||
node: namedExportNode,
|
||||
messageId: "single"
|
||||
});
|
||||
else if (target === "any" && specifierExportCount > 0) context.report({
|
||||
node: namedExportNode,
|
||||
messageId: "any"
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
export { prefer_default_export_default as t };
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { n as isPlainObject, t as toMerged } from "./vender.mjs";
|
||||
//#region src/utils/index.ts
|
||||
function createRule({ name, create, defaultOptions = [], meta }) {
|
||||
return {
|
||||
create: ((context) => {
|
||||
const optionsCount = Math.max(context.options.length, defaultOptions.length);
|
||||
return create(context, Array.from({ length: optionsCount }, (_, i) => {
|
||||
if (isPlainObject(context.options[i]) && isPlainObject(defaultOptions[i])) return toMerged(defaultOptions[i], context.options[i]);
|
||||
return context.options[i] ?? defaultOptions[i];
|
||||
}));
|
||||
}),
|
||||
defaultOptions,
|
||||
meta: {
|
||||
...meta,
|
||||
docs: {
|
||||
...meta.docs,
|
||||
url: `https://github.com/9romise/eslint-plugin-import-lite/blob/main/src/rules/${name}/README.md`
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
//#region src/utils/ast.ts
|
||||
function isCommaToken(token) {
|
||||
return token.type === "Punctuator" && token.value === ",";
|
||||
}
|
||||
function getValue(node) {
|
||||
switch (node.type) {
|
||||
case "Identifier": return node.name;
|
||||
case "Literal": return node.value;
|
||||
default: throw new Error(`Unsupported node type: ${node.type}`);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
//#region src/utils/compat.ts
|
||||
function sourceType(context) {
|
||||
if (context.parserOptions && "sourceType" in context.parserOptions) return context.parserOptions.sourceType;
|
||||
if (context.languageOptions) return context.languageOptions.sourceType;
|
||||
}
|
||||
//#endregion
|
||||
//#region src/utils/resolve.ts
|
||||
function resolve(path) {
|
||||
return path;
|
||||
}
|
||||
//#endregion
|
||||
export { createRule as a, isCommaToken as i, sourceType as n, getValue as r, resolve as t };
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
//#region node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/predicate/isPrimitive.mjs
|
||||
function isPrimitive(value) {
|
||||
return value == null || typeof value !== "object" && typeof value !== "function";
|
||||
}
|
||||
//#endregion
|
||||
//#region node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/predicate/isTypedArray.mjs
|
||||
function isTypedArray(x) {
|
||||
return ArrayBuffer.isView(x) && !(x instanceof DataView);
|
||||
}
|
||||
//#endregion
|
||||
//#region node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/object/clone.mjs
|
||||
function clone(obj) {
|
||||
if (isPrimitive(obj)) return obj;
|
||||
if (Array.isArray(obj) || isTypedArray(obj) || obj instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && obj instanceof SharedArrayBuffer) return obj.slice(0);
|
||||
const prototype = Object.getPrototypeOf(obj);
|
||||
if (prototype == null) return Object.assign(Object.create(prototype), obj);
|
||||
const Constructor = prototype.constructor;
|
||||
if (obj instanceof Date || obj instanceof Map || obj instanceof Set) return new Constructor(obj);
|
||||
if (obj instanceof RegExp) {
|
||||
const newRegExp = new Constructor(obj);
|
||||
newRegExp.lastIndex = obj.lastIndex;
|
||||
return newRegExp;
|
||||
}
|
||||
if (obj instanceof DataView) return new Constructor(obj.buffer.slice(0));
|
||||
if (obj instanceof Error) {
|
||||
let newError;
|
||||
if (obj instanceof AggregateError) newError = new Constructor(obj.errors, obj.message, { cause: obj.cause });
|
||||
else newError = new Constructor(obj.message, { cause: obj.cause });
|
||||
newError.stack = obj.stack;
|
||||
Object.assign(newError, obj);
|
||||
return newError;
|
||||
}
|
||||
if (typeof File !== "undefined" && obj instanceof File) return new Constructor([obj], obj.name, {
|
||||
type: obj.type,
|
||||
lastModified: obj.lastModified
|
||||
});
|
||||
if (typeof obj === "object") {
|
||||
const newObject = Object.create(prototype);
|
||||
return Object.assign(newObject, obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
//#endregion
|
||||
//#region node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
|
||||
function isPlainObject(value) {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
const proto = Object.getPrototypeOf(value);
|
||||
if (!(proto === null || proto === Object.prototype || Object.getPrototypeOf(proto) === null)) return false;
|
||||
return Object.prototype.toString.call(value) === "[object Object]";
|
||||
}
|
||||
//#endregion
|
||||
//#region node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
|
||||
function isUnsafeProperty(key) {
|
||||
return key === "__proto__";
|
||||
}
|
||||
//#endregion
|
||||
//#region node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/object/mergeWith.mjs
|
||||
function mergeWith(target, source, merge) {
|
||||
const sourceKeys = Object.keys(source);
|
||||
for (let i = 0; i < sourceKeys.length; i++) {
|
||||
const key = sourceKeys[i];
|
||||
if (isUnsafeProperty(key)) continue;
|
||||
const sourceValue = source[key];
|
||||
const targetValue = target[key];
|
||||
const merged = merge(targetValue, sourceValue, key, target, source);
|
||||
if (merged !== void 0) target[key] = merged;
|
||||
else if (Array.isArray(sourceValue)) if (Array.isArray(targetValue)) target[key] = mergeWith(targetValue, sourceValue, merge);
|
||||
else target[key] = mergeWith([], sourceValue, merge);
|
||||
else if (isPlainObject(sourceValue)) if (isPlainObject(targetValue)) target[key] = mergeWith(targetValue, sourceValue, merge);
|
||||
else target[key] = mergeWith({}, sourceValue, merge);
|
||||
else if (targetValue === void 0 || sourceValue !== void 0) target[key] = sourceValue;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
//#endregion
|
||||
//#region node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/object/toMerged.mjs
|
||||
function toMerged(target, source) {
|
||||
return mergeWith(clone(target), source, function mergeRecursively(targetValue, sourceValue) {
|
||||
if (Array.isArray(sourceValue)) if (Array.isArray(targetValue)) return mergeWith(clone(targetValue), sourceValue, mergeRecursively);
|
||||
else return mergeWith([], sourceValue, mergeRecursively);
|
||||
else if (isPlainObject(sourceValue)) if (isPlainObject(targetValue)) return mergeWith(clone(targetValue), sourceValue, mergeRecursively);
|
||||
else return mergeWith({}, sourceValue, mergeRecursively);
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
export { isPlainObject as n, toMerged as t };
|
||||
Reference in New Issue
Block a user