UNPKG

@soleil-se/eslint-config

Version:

ESLint configuration for Sitevision apps and projects.

129 lines (111 loc) 3.57 kB
/** @import { Linter, Rule } from 'eslint' */ /** * @typedef ImportNamingOptions * @property {string[]} packages */ /** @param {string} source */ function isPackageImport(source) { return !source.startsWith('.') && !source.startsWith('/'); } /** @param {string} source @param {string} packageName */ function matchesPackagePrefix(source, packageName) { return source === packageName || source.startsWith(`${packageName}/`); } /** @param {string} source @param {ImportNamingOptions} settings */ function isIncludedImport(source, settings) { return settings.packages.some( (packageName) => matchesPackagePrefix(source, packageName), ); } /** @param {string} source @param {ImportNamingOptions} settings */ function getExpectedImportName(source, settings) { if (!isPackageImport(source) || !isIncludedImport(source, settings)) return undefined; const moduleName = source.split('/').at(-1); return moduleName && /^[$A-Z_a-z][$\w]*$/.test(moduleName) ? moduleName : undefined; } /** @type {Rule.RuleModule} */ const matchPackageDefaultImportNameRule = { meta: { type: 'suggestion', fixable: 'code', schema: [ { type: 'object', properties: { packages: { type: 'array', items: { type: 'string', }, }, }, additionalProperties: false, }, ], messages: { mismatch: 'Default import from {{source}} should be named {{expected}}.', conflict: [ 'Default import from {{source}} should be named {{expected}},', 'but that name is already used in this scope.', ].join(' '), }, }, create(context) { const { sourceCode } = context; /** @type {ImportNamingOptions} */ const settings = { packages: context.options[0]?.packages ?? [], }; return { ImportDeclaration(node) { if (typeof node.source.value !== 'string') return; const defaultSpecifier = node.specifiers.find( (specifier) => specifier.type === 'ImportDefaultSpecifier', ); if (!defaultSpecifier) return; const expectedName = getExpectedImportName(node.source.value, settings); if (!expectedName) return; if (defaultSpecifier.local.name === expectedName) return; const [variable] = sourceCode.getDeclaredVariables(defaultSpecifier); if (!variable) return; const conflictingVariable = variable.scope.set.get(expectedName); if (conflictingVariable && conflictingVariable !== variable) { context.report({ node: defaultSpecifier.local, messageId: 'conflict', data: { expected: expectedName, source: node.source.value, }, }); return; } context.report({ node: defaultSpecifier.local, messageId: 'mismatch', data: { expected: expectedName, source: node.source.value, }, fix(fixer) { const identifiers = [ defaultSpecifier.local, ...variable.references.map((reference) => reference.identifier), ]; return identifiers.map((identifier) => fixer.replaceText(identifier, expectedName)); }, }); }, }; }, }; /** @type {Linter.Plugin} */ const importNamingPlugin = { rules: { 'match-package-default-import-name': matchPackageDefaultImportNameRule, }, }; export default importNamingPlugin;