@masknet/eslint-plugin
Version:
eslint plugin for masknet
163 lines • 7.21 kB
JavaScript
import { AST_NODE_TYPES } from '@typescript-eslint/types';
import { createRule } from '../rule.js';
export default createRule({
name: 'prefer-defer-import',
meta: {
type: 'problem',
fixable: 'code',
docs: {
description: 'Prefer defer import a module. See <https://github.com/tc39/proposal-defer-import-eval> and <https://github.com/webpack/webpack/pull/16567/>.',
},
schema: [
{
type: 'object',
oneOf: [
{
type: 'object',
additionalProperties: false,
properties: {
deferPackages: { type: 'array', items: { type: 'string' }, minItems: 0 },
syntax: { type: 'string', enum: ['webpack-magic-comment'] },
},
},
{
type: 'object',
additionalProperties: false,
properties: {
eagerPackages: { type: 'array', items: { type: 'string' }, minItems: 0 },
syntax: { type: 'string', enum: ['webpack-magic-comment'] },
},
},
],
},
],
messages: {
prefer: 'Prefer import the package with defer syntax.',
},
},
resolveOptions(options) {
if (options?.deferPackages && options.eagerPackages)
throw new Error('Cannot use both deferPackages and eagerPackages in the rule prefer-defer-import.');
return options;
},
create(context, options) {
function handle(node) {
if (!needFix(node, context.sourceCode, options))
return;
const fix = makeFixer(context, node);
context.report({ node, messageId: 'prefer', fix });
}
return {
ImportDeclaration: handle,
ExportAllDeclaration: handle,
ExportNamedDeclaration: handle,
};
},
});
function needFix(node, code, options) {
if (!node.source)
return false;
if (node.type === AST_NODE_TYPES.ImportDeclaration && (node.importKind === 'type' || node.specifiers.length === 0))
return false;
const [char] = node.source.value;
// nodejs import map, relative url, absolute url
if (char === '#' || char === '.' || char === '/')
return false;
if (node.type === AST_NODE_TYPES.ExportNamedDeclaration &&
(node.exportKind === 'type' || node.specifiers.length === 0))
return false;
if (options.deferPackages && !options.deferPackages.includes(node.source.value))
return false;
if (options.eagerPackages?.includes(node.source.value))
return false;
for (const comment of code.getCommentsInside(node)) {
if (comment.value.includes('webpackDefer: true'))
return false;
}
return true;
}
function makeFixer(context, node) {
if (node.type === AST_NODE_TYPES.ExportNamedDeclaration || !node.source)
return null;
if (context.sourceCode
.getDeclaredVariables(node)
.some((x) => x.references.some((x) => x.identifier.parent?.type === AST_NODE_TYPES.ExportSpecifier)))
return null;
return function* (fixer) {
if (node.type === AST_NODE_TYPES.ExportAllDeclaration && node.exported) {
yield fixer.insertTextBefore(node.source, `/* webpackDefer: true */ `);
}
if (node.type !== AST_NODE_TYPES.ImportDeclaration)
return;
// reuse the name from the original import if possible
const suggestedName = node.specifiers.find((x) => x.type === AST_NODE_TYPES.ImportNamespaceSpecifier)?.local.name ??
node.specifiers.find((x) => x.type === AST_NODE_TYPES.ImportDefaultSpecifier)?.local.name ??
node.source.value.replaceAll(/[^\dA-Za-z]/g, '_');
{
let imports = `import * as ${suggestedName} from /* webpackDefer: true */ ${node.source.raw}`;
let assertions = '';
const assertionList = [];
if (node.assertions.length > 0) {
assertions += ` assert {`;
for (const assertion of node.assertions) {
assertionList.push(` ${assertion.key.type === AST_NODE_TYPES.Literal ? assertion.key.raw : assertion.key.name}: ${assertion.value.raw}`);
}
assertions += assertionList.join(', ') + ' }';
}
imports += assertions;
imports += ';';
let typeOnlyImports = 'import type { ';
const typeOnlyImportList = [];
let useTypeOnlyImports = false;
for (const spec of node.specifiers) {
if (spec.type === AST_NODE_TYPES.ImportSpecifier && spec.importKind === 'type') {
useTypeOnlyImports = true;
typeOnlyImportList.push(spec.imported.name === spec.local.name ? spec.imported.name : `${spec.imported.name} as ${spec.local.name}`);
}
}
if (useTypeOnlyImports) {
typeOnlyImports += typeOnlyImportList.join(', ') + ' } from ' + node.source.raw + assertions + ';';
imports += ' ';
imports += typeOnlyImports;
}
if (node.specifiers.every((x) => x.type === AST_NODE_TYPES.ImportSpecifier && x.importKind === 'type')) {
yield fixer.replaceText(node, typeOnlyImports);
return;
}
yield fixer.replaceText(node, imports);
}
for (const spec of node.specifiers) {
if (spec.type === AST_NODE_TYPES.ImportNamespaceSpecifier)
continue;
if (spec.type === AST_NODE_TYPES.ImportDefaultSpecifier) {
yield* replaceAllReference(context, spec, `${suggestedName}.default`, fixer);
}
else {
if (spec.importKind === 'type')
continue;
const imported = spec.imported.name;
yield* replaceAllReference(context, spec, /^\p{ID_Start}\p{ID_Continue}{0,}$/u.test(imported)
? `${suggestedName}.${imported}`
: `${suggestedName}[${JSON.stringify(imported)}]`, fixer);
}
}
};
}
function* replaceAllReference(context, node, replacement, fixer) {
const variables = context.sourceCode.getDeclaredVariables(node);
if (variables.length !== 1)
throw new Error('Expected exactly one reference');
if (replacement === variables[0].name)
return;
for (const reference of variables[0].references) {
const id = reference.identifier;
// eslint-disable-next-line unicorn/prefer-ternary
if (id.parent?.type === AST_NODE_TYPES.Property && id.parent.shorthand && id.parent.value === id) {
yield fixer.replaceText(id.parent, `${id.name}: ${replacement}`);
}
else {
yield fixer.replaceText(id, replacement);
}
}
}
//# sourceMappingURL=prefer-defer-import.js.map