first commit
This commit is contained in:
302
node_modules/eslint-plugin-import-x/lib/rules/extensions.js
generated
vendored
Normal file
302
node_modules/eslint-plugin-import-x/lib/rules/extensions.js
generated
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
import path from 'node:path';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { isBuiltIn, isExternalModule, isScoped, createRule, moduleVisitor, resolve, parsePath, stringifyPath, } from '../utils/index.js';
|
||||
const modifierValues = ['always', 'ignorePackages', 'never'];
|
||||
const modifierSchema = {
|
||||
type: 'string',
|
||||
enum: [...modifierValues],
|
||||
};
|
||||
const modifierByFileExtensionSchema = {
|
||||
type: 'object',
|
||||
patternProperties: { '.*': modifierSchema },
|
||||
};
|
||||
const properties = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pattern: modifierByFileExtensionSchema,
|
||||
ignorePackages: {
|
||||
type: 'boolean',
|
||||
},
|
||||
checkTypeImports: {
|
||||
type: 'boolean',
|
||||
},
|
||||
pathGroupOverrides: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pattern: { type: 'string' },
|
||||
patternOptions: { type: 'object' },
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['enforce', 'ignore'],
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
required: ['pattern', 'action'],
|
||||
},
|
||||
},
|
||||
fix: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
};
|
||||
function buildProperties(context) {
|
||||
const result = {
|
||||
defaultConfig: 'never',
|
||||
pattern: {},
|
||||
ignorePackages: false,
|
||||
checkTypeImports: false,
|
||||
pathGroupOverrides: [],
|
||||
fix: false,
|
||||
};
|
||||
for (const obj of context.options) {
|
||||
if (typeof obj === 'string') {
|
||||
result.defaultConfig = obj;
|
||||
continue;
|
||||
}
|
||||
if (typeof obj !== 'object' || !obj) {
|
||||
continue;
|
||||
}
|
||||
if (obj.fix != null) {
|
||||
result.fix = Boolean(obj.fix);
|
||||
}
|
||||
if ((!('pattern' in obj) || obj.pattern == null) &&
|
||||
obj.ignorePackages == null &&
|
||||
obj.checkTypeImports == null) {
|
||||
Object.assign(result.pattern, obj);
|
||||
continue;
|
||||
}
|
||||
if ('pattern' in obj && obj.pattern != null) {
|
||||
Object.assign(result.pattern, obj.pattern);
|
||||
}
|
||||
if (typeof obj.ignorePackages === 'boolean') {
|
||||
result.ignorePackages = obj.ignorePackages;
|
||||
}
|
||||
if (typeof obj.checkTypeImports === 'boolean') {
|
||||
result.checkTypeImports = obj.checkTypeImports;
|
||||
}
|
||||
if (Array.isArray(obj.pathGroupOverrides)) {
|
||||
result.pathGroupOverrides = obj.pathGroupOverrides;
|
||||
}
|
||||
}
|
||||
if (result.defaultConfig === 'ignorePackages') {
|
||||
result.defaultConfig = 'always';
|
||||
result.ignorePackages = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function isExternalRootModule(file) {
|
||||
if (file === '.' || file === '..') {
|
||||
return false;
|
||||
}
|
||||
const slashCount = file.split('/').length - 1;
|
||||
return slashCount === 0 || (isScoped(file) && slashCount <= 1);
|
||||
}
|
||||
function computeOverrideAction(pathGroupOverrides, path) {
|
||||
for (const { pattern, patternOptions, action } of pathGroupOverrides) {
|
||||
if (minimatch(path, pattern, patternOptions || { nocomment: true })) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
function replaceImportPath(source, importPath) {
|
||||
return source.replace(/^(['"])(.+)\1$/, (_, quote) => `${quote}${importPath}${quote}`);
|
||||
}
|
||||
export default createRule({
|
||||
name: 'extensions',
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
category: 'Style guide',
|
||||
description: 'Ensure consistent use of file extension within the import path.',
|
||||
},
|
||||
fixable: 'code',
|
||||
hasSuggestions: true,
|
||||
schema: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'array',
|
||||
items: [modifierSchema],
|
||||
additionalItems: false,
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
items: [modifierSchema, properties],
|
||||
additionalItems: false,
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
items: [properties],
|
||||
additionalItems: false,
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
items: [modifierSchema, modifierByFileExtensionSchema],
|
||||
additionalItems: false,
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
items: [modifierByFileExtensionSchema],
|
||||
additionalItems: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
messages: {
|
||||
missing: 'Missing file extension for "{{importPath}}"',
|
||||
missingKnown: 'Missing file extension "{{extension}}" for "{{importPath}}"',
|
||||
unexpected: 'Unexpected use of file extension "{{extension}}" for "{{importPath}}"',
|
||||
addMissing: 'Add "{{extension}}" file extension from "{{importPath}}" into "{{fixedImportPath}}"',
|
||||
removeUnexpected: 'Remove unexpected "{{extension}}" file extension from "{{importPath}}" into "{{fixedImportPath}}"',
|
||||
},
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
const props = buildProperties(context);
|
||||
function getModifier(extension) {
|
||||
return props.pattern[extension] || props.defaultConfig;
|
||||
}
|
||||
function isUseOfExtensionRequired(extension, isPackage) {
|
||||
return (getModifier(extension) === 'always' &&
|
||||
(!props.ignorePackages || !isPackage));
|
||||
}
|
||||
function isUseOfExtensionForbidden(extension) {
|
||||
return getModifier(extension) === 'never';
|
||||
}
|
||||
function isResolvableWithoutExtension(file) {
|
||||
const extension = path.extname(file);
|
||||
const fileWithoutExtension = file.slice(0, -extension.length);
|
||||
const resolvedFileWithoutExtension = resolve(fileWithoutExtension, context);
|
||||
return resolvedFileWithoutExtension === resolve(file, context);
|
||||
}
|
||||
return moduleVisitor((source, node) => {
|
||||
if (!source || !source.value) {
|
||||
return;
|
||||
}
|
||||
const importPathWithQueryString = source.value;
|
||||
const overrideAction = computeOverrideAction(props.pathGroupOverrides || [], importPathWithQueryString);
|
||||
if (overrideAction === 'ignore') {
|
||||
return;
|
||||
}
|
||||
if (!overrideAction &&
|
||||
isBuiltIn(importPathWithQueryString, context.settings)) {
|
||||
return;
|
||||
}
|
||||
const { pathname: importPath, query, hash, } = parsePath(importPathWithQueryString);
|
||||
if (!overrideAction && isExternalRootModule(importPath)) {
|
||||
return;
|
||||
}
|
||||
const resolvedPath = resolve(importPath, context);
|
||||
const extension = path.extname(resolvedPath || importPath).slice(1);
|
||||
const isPackage = isExternalModule(importPath, resolve(importPath, context), context) || isScoped(importPath);
|
||||
if (!extension || !importPath.endsWith(`.${extension}`)) {
|
||||
if (!props.checkTypeImports &&
|
||||
(('importKind' in node && node.importKind === 'type') ||
|
||||
('exportKind' in node && node.exportKind === 'type'))) {
|
||||
return;
|
||||
}
|
||||
const extensionRequired = isUseOfExtensionRequired(extension, !overrideAction && isPackage);
|
||||
const extensionForbidden = isUseOfExtensionForbidden(extension);
|
||||
if (extensionRequired && !extensionForbidden) {
|
||||
const fixedImportPath = stringifyPath({
|
||||
pathname: `${/([\\/]|[\\/]?\.?\.)$/.test(importPath)
|
||||
? `${importPath.endsWith('/')
|
||||
? importPath.slice(0, -1)
|
||||
: importPath}/index.${extension}`
|
||||
: `${importPath}.${extension}`}`,
|
||||
query,
|
||||
hash,
|
||||
});
|
||||
const fixOrSuggest = {
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(source, replaceImportPath(source.raw, fixedImportPath));
|
||||
},
|
||||
};
|
||||
context.report({
|
||||
node: source,
|
||||
messageId: extension ? 'missingKnown' : 'missing',
|
||||
data: {
|
||||
extension,
|
||||
importPath: importPathWithQueryString,
|
||||
},
|
||||
...(extension &&
|
||||
(props.fix
|
||||
? fixOrSuggest
|
||||
: {
|
||||
suggest: [
|
||||
{
|
||||
...fixOrSuggest,
|
||||
messageId: 'addMissing',
|
||||
data: {
|
||||
extension,
|
||||
importPath: importPathWithQueryString,
|
||||
fixedImportPath,
|
||||
},
|
||||
},
|
||||
],
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (extension &&
|
||||
isUseOfExtensionForbidden(extension) &&
|
||||
isResolvableWithoutExtension(importPath)) {
|
||||
const fixedPathname = importPath.slice(0, -(extension.length + 1));
|
||||
const isIndex = fixedPathname.endsWith('/index');
|
||||
const fixedImportPath = stringifyPath({
|
||||
pathname: isIndex ? fixedPathname.slice(0, -6) : fixedPathname,
|
||||
query,
|
||||
hash,
|
||||
});
|
||||
const fixOrSuggest = {
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(source, replaceImportPath(source.raw, fixedImportPath));
|
||||
},
|
||||
};
|
||||
const commonSuggestion = {
|
||||
...fixOrSuggest,
|
||||
messageId: 'removeUnexpected',
|
||||
data: {
|
||||
extension,
|
||||
importPath: importPathWithQueryString,
|
||||
fixedImportPath,
|
||||
},
|
||||
};
|
||||
context.report({
|
||||
node: source,
|
||||
messageId: 'unexpected',
|
||||
data: {
|
||||
extension,
|
||||
importPath: importPathWithQueryString,
|
||||
},
|
||||
...(props.fix
|
||||
? fixOrSuggest
|
||||
: {
|
||||
suggest: [
|
||||
commonSuggestion,
|
||||
isIndex && {
|
||||
...commonSuggestion,
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(source, replaceImportPath(source.raw, stringifyPath({
|
||||
pathname: fixedPathname,
|
||||
query,
|
||||
hash,
|
||||
})));
|
||||
},
|
||||
data: {
|
||||
...commonSuggestion.data,
|
||||
fixedImportPath: stringifyPath({
|
||||
pathname: fixedPathname,
|
||||
query,
|
||||
hash,
|
||||
}),
|
||||
},
|
||||
},
|
||||
].filter(Boolean),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}, { commonjs: true });
|
||||
},
|
||||
});
|
||||
//# sourceMappingURL=extensions.js.map
|
||||
Reference in New Issue
Block a user