Path: blob/main/src/rules/no-namespace.js
829 views
/**1* @fileoverview Rule to disallow namespace import2* @author Radek Benkel3*/45import minimatch from 'minimatch';6import docsUrl from '../docsUrl';78//------------------------------------------------------------------------------9// Rule Definition10//------------------------------------------------------------------------------111213module.exports = {14meta: {15type: 'suggestion',16docs: {17url: docsUrl('no-namespace'),18},19fixable: 'code',20schema: [{21type: 'object',22properties: {23ignore: {24type: 'array',25items: {26type: 'string',27},28uniqueItems: true,29},30},31}],32},3334create(context) {35const firstOption = context.options[0] || {};36const ignoreGlobs = firstOption.ignore;3738return {39ImportNamespaceSpecifier(node) {40if (ignoreGlobs && ignoreGlobs.find(glob => minimatch(node.parent.source.value, glob, { matchBase: true }))) {41return;42}4344const scopeVariables = context.getScope().variables;45const namespaceVariable = scopeVariables.find((variable) => variable.defs[0].node === node);46const namespaceReferences = namespaceVariable.references;47const namespaceIdentifiers = namespaceReferences.map(reference => reference.identifier);48const canFix = namespaceIdentifiers.length > 0 && !usesNamespaceAsObject(namespaceIdentifiers);4950context.report({51node,52message: `Unexpected namespace import.`,53fix: canFix && (fixer => {54const scopeManager = context.getSourceCode().scopeManager;55const fixes = [];5657// Pass 1: Collect variable names that are already in scope for each reference we want58// to transform, so that we can be sure that we choose non-conflicting import names59const importNameConflicts = {};60namespaceIdentifiers.forEach((identifier) => {61const parent = identifier.parent;62if (parent && parent.type === 'MemberExpression') {63const importName = getMemberPropertyName(parent);64const localConflicts = getVariableNamesInScope(scopeManager, parent);65if (!importNameConflicts[importName]) {66importNameConflicts[importName] = localConflicts;67} else {68localConflicts.forEach((c) => importNameConflicts[importName].add(c));69}70}71});7273// Choose new names for each import74const importNames = Object.keys(importNameConflicts);75const importLocalNames = generateLocalNames(76importNames,77importNameConflicts,78namespaceVariable.name,79);8081// Replace the ImportNamespaceSpecifier with a list of ImportSpecifiers82const namedImportSpecifiers = importNames.map((importName) => (83importName === importLocalNames[importName]84? importName85: `${importName} as ${importLocalNames[importName]}`86));87fixes.push(fixer.replaceText(node, `{ ${namedImportSpecifiers.join(', ')} }`));8889// Pass 2: Replace references to the namespace with references to the named imports90namespaceIdentifiers.forEach((identifier) => {91const parent = identifier.parent;92if (parent && parent.type === 'MemberExpression') {93const importName = getMemberPropertyName(parent);94fixes.push(fixer.replaceText(parent, importLocalNames[importName]));95}96});9798return fixes;99}),100});101},102};103},104};105106/**107* @param {Identifier[]} namespaceIdentifiers108* @returns {boolean} `true` if the namespace variable is more than just a glorified constant109*/110function usesNamespaceAsObject(namespaceIdentifiers) {111return !namespaceIdentifiers.every((identifier) => {112const parent = identifier.parent;113114// `namespace.x` or `namespace['x']`115return (116parent && parent.type === 'MemberExpression' &&117(parent.property.type === 'Identifier' || parent.property.type === 'Literal')118);119});120}121122/**123* @param {MemberExpression} memberExpression124* @returns {string} the name of the member in the object expression, e.g. the `x` in `namespace.x`125*/126function getMemberPropertyName(memberExpression) {127return memberExpression.property.type === 'Identifier'128? memberExpression.property.name129: memberExpression.property.value;130}131132/**133* @param {ScopeManager} scopeManager134* @param {ASTNode} node135* @return {Set<string>}136*/137function getVariableNamesInScope(scopeManager, node) {138let currentNode = node;139let scope = scopeManager.acquire(currentNode);140while (scope == null) {141currentNode = currentNode.parent;142scope = scopeManager.acquire(currentNode, true);143}144return new Set([145...scope.variables.map(variable => variable.name),146...scope.upper.variables.map(variable => variable.name),147]);148}149150/**151*152* @param {*} names153* @param {*} nameConflicts154* @param {*} namespaceName155*/156function generateLocalNames(names, nameConflicts, namespaceName) {157const localNames = {};158names.forEach((name) => {159let localName;160if (!nameConflicts[name].has(name)) {161localName = name;162} else if (!nameConflicts[name].has(`${namespaceName}_${name}`)) {163localName = `${namespaceName}_${name}`;164} else {165for (let i = 1; i < Infinity; i++) {166if (!nameConflicts[name].has(`${namespaceName}_${name}_${i}`)) {167localName = `${namespaceName}_${name}_${i}`;168break;169}170}171}172localNames[name] = localName;173});174return localNames;175}176177178