import path from 'path';
import resolve from 'eslint-module-utils/resolve';
import { isBuiltIn, isExternalModule, isScoped } from '../core/importType';
import moduleVisitor from 'eslint-module-utils/moduleVisitor';
import docsUrl from '../docsUrl';
const enumValues = { enum: [ 'always', 'ignorePackages', 'never' ] };
const patternProperties = {
type: 'object',
patternProperties: { '.*': enumValues },
};
const properties = {
type: 'object',
properties: {
'pattern': patternProperties,
'ignorePackages': { type: 'boolean' },
},
};
function buildProperties(context) {
const result = {
defaultConfig: 'never',
pattern: {},
ignorePackages: false,
};
context.options.forEach(obj => {
if (typeof obj === 'string') {
result.defaultConfig = obj;
return;
}
if (obj.pattern === undefined && obj.ignorePackages === undefined) {
Object.assign(result.pattern, obj);
return;
}
if (obj.pattern !== undefined) {
Object.assign(result.pattern, obj.pattern);
}
if (obj.ignorePackages !== undefined) {
result.ignorePackages = obj.ignorePackages;
}
});
if (result.defaultConfig === 'ignorePackages') {
result.defaultConfig = 'always';
result.ignorePackages = true;
}
return result;
}
module.exports = {
meta: {
type: 'suggestion',
docs: {
url: docsUrl('extensions'),
},
schema: {
anyOf: [
{
type: 'array',
items: [enumValues],
additionalItems: false,
},
{
type: 'array',
items: [
enumValues,
properties,
],
additionalItems: false,
},
{
type: 'array',
items: [properties],
additionalItems: false,
},
{
type: 'array',
items: [patternProperties],
additionalItems: false,
},
{
type: 'array',
items: [
enumValues,
patternProperties,
],
additionalItems: false,
},
],
},
},
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);
}
function isExternalRootModule(file) {
const slashCount = file.split('/').length - 1;
if (slashCount === 0) return true;
if (isScoped(file) && slashCount <= 1) return true;
return false;
}
function checkFileExtension(source, node) {
if (!source || !source.value) return;
const importPathWithQueryString = source.value;
if (isBuiltIn(importPathWithQueryString, context.settings)) return;
const importPath = importPathWithQueryString.replace(/\?(.*)$/, '');
if (isExternalRootModule(importPath)) return;
const resolvedPath = resolve(importPath, context);
const extension = path.extname(resolvedPath || importPath).substring(1);
const isPackage = isExternalModule(
importPath,
context.settings,
resolve(importPath, context),
context,
) || isScoped(importPath);
if (!extension || !importPath.endsWith(`.${extension}`)) {
if (node.importKind === 'type') return;
const extensionRequired = isUseOfExtensionRequired(extension, isPackage);
const extensionForbidden = isUseOfExtensionForbidden(extension);
if (extensionRequired && !extensionForbidden) {
context.report({
node: source,
message:
`Missing file extension ${extension ? `"${extension}" ` : ''}for "${importPathWithQueryString}"`,
});
}
} else if (extension) {
if (isUseOfExtensionForbidden(extension) && isResolvableWithoutExtension(importPath)) {
context.report({
node: source,
message: `Unexpected use of file extension "${extension}" for "${importPathWithQueryString}"`,
});
}
}
}
return moduleVisitor(checkFileExtension, { commonjs: true });
},
};