| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 | 'use strict';const {isOpeningParenToken} = require('@eslint-community/eslint-utils');const isShadowed = require('./utils/is-shadowed.js');const assertToken = require('./utils/assert-token.js');const {referenceIdentifierSelector} = require('./selectors/index.js');const {isStaticRequire} = require('./ast/index.js');const {	removeParentheses,	replaceReferenceIdentifier,	removeSpacesAfter,} = require('./fix/index.js');const ERROR_USE_STRICT_DIRECTIVE = 'error/use-strict-directive';const ERROR_GLOBAL_RETURN = 'error/global-return';const ERROR_IDENTIFIER = 'error/identifier';const SUGGESTION_USE_STRICT_DIRECTIVE = 'suggestion/use-strict-directive';const SUGGESTION_DIRNAME = 'suggestion/dirname';const SUGGESTION_FILENAME = 'suggestion/filename';const SUGGESTION_IMPORT = 'suggestion/import';const SUGGESTION_EXPORT = 'suggestion/export';const messages = {	[ERROR_USE_STRICT_DIRECTIVE]: 'Do not use "use strict" directive.',	[ERROR_GLOBAL_RETURN]: '"return" should be used inside a function.',	[ERROR_IDENTIFIER]: 'Do not use "{{name}}".',	[SUGGESTION_USE_STRICT_DIRECTIVE]: 'Remove "use strict" directive.',	[SUGGESTION_DIRNAME]: 'Replace "__dirname" with `"…(import.meta.url)"`.',	[SUGGESTION_FILENAME]: 'Replace "__filename" with `"…(import.meta.url)"`.',	[SUGGESTION_IMPORT]: 'Switch to `import`.',	[SUGGESTION_EXPORT]: 'Switch to `export`.',};const identifierSelector = referenceIdentifierSelector([	'exports',	'require',	'module',	'__filename',	'__dirname',]);function fixRequireCall(node, sourceCode) {	if (!isStaticRequire(node.parent) || node.parent.callee !== node) {		return;	}	const requireCall = node.parent;	const {		parent,		callee,		arguments: [source],	} = requireCall;	// `require("foo")`	if (parent.type === 'ExpressionStatement' && parent.parent.type === 'Program') {		return function * (fixer) {			yield fixer.replaceText(callee, 'import');			const openingParenthesisToken = sourceCode.getTokenAfter(				callee,				isOpeningParenToken,			);			yield fixer.replaceText(openingParenthesisToken, ' ');			const closingParenthesisToken = sourceCode.getLastToken(requireCall);			yield fixer.remove(closingParenthesisToken);			for (const node of [callee, requireCall, source]) {				yield * removeParentheses(node, fixer, sourceCode);			}		};	}	// `const foo = require("foo")`	// `const {foo} = require("foo")`	if (		parent.type === 'VariableDeclarator'		&& parent.init === requireCall		&& (			parent.id.type === 'Identifier'			|| (				parent.id.type === 'ObjectPattern'				&& parent.id.properties.every(					({type, key, value, computed}) =>						type === 'Property'						&& !computed						&& value.type === 'Identifier'						&& key.type === 'Identifier',				)			)		)		&& parent.parent.type === 'VariableDeclaration'		&& parent.parent.kind === 'const'		&& parent.parent.declarations.length === 1		&& parent.parent.declarations[0] === parent		&& parent.parent.parent.type === 'Program'	) {		const declarator = parent;		const declaration = declarator.parent;		const {id} = declarator;		return function * (fixer) {			const constToken = sourceCode.getFirstToken(declaration);			assertToken(constToken, {				expected: {type: 'Keyword', value: 'const'},				ruleId: 'prefer-module',			});			yield fixer.replaceText(constToken, 'import');			const equalToken = sourceCode.getTokenAfter(id);			assertToken(equalToken, {				expected: {type: 'Punctuator', value: '='},				ruleId: 'prefer-module',			});			yield removeSpacesAfter(id, sourceCode, fixer);			yield removeSpacesAfter(equalToken, sourceCode, fixer);			yield fixer.replaceText(equalToken, ' from ');			yield fixer.remove(callee);			const openingParenthesisToken = sourceCode.getTokenAfter(				callee,				isOpeningParenToken,			);			yield fixer.remove(openingParenthesisToken);			const closingParenthesisToken = sourceCode.getLastToken(requireCall);			yield fixer.remove(closingParenthesisToken);			for (const node of [callee, requireCall, source]) {				yield * removeParentheses(node, fixer, sourceCode);			}			if (id.type === 'Identifier') {				return;			}			const {properties} = id;			for (const property of properties) {				const {key, shorthand} = property;				if (!shorthand) {					const commaToken = sourceCode.getTokenAfter(key);					assertToken(commaToken, {						expected: {type: 'Punctuator', value: ':'},						ruleId: 'prefer-module',					});					yield removeSpacesAfter(key, sourceCode, fixer);					yield removeSpacesAfter(commaToken, sourceCode, fixer);					yield fixer.replaceText(commaToken, ' as ');				}			}		};	}}const isTopLevelAssignment = node =>	node.parent.type === 'AssignmentExpression'	&& node.parent.operator === '='	&& node.parent.left === node	&& node.parent.parent.type === 'ExpressionStatement'	&& node.parent.parent.parent.type === 'Program';const isNamedExport = node =>	node.parent.type === 'MemberExpression'	&& !node.parent.optional	&& !node.parent.computed	&& node.parent.object === node	&& node.parent.property.type === 'Identifier'	&& isTopLevelAssignment(node.parent)	&& node.parent.parent.right.type === 'Identifier';const isModuleExports = node =>	node.parent.type === 'MemberExpression'	&& !node.parent.optional	&& !node.parent.computed	&& node.parent.object === node	&& node.parent.property.type === 'Identifier'	&& node.parent.property.name === 'exports';function fixDefaultExport(node, sourceCode) {	return function * (fixer) {		yield fixer.replaceText(node, 'export default ');		yield removeSpacesAfter(node, sourceCode, fixer);		const equalToken = sourceCode.getTokenAfter(node, token => token.type === 'Punctuator' && token.value === '=');		yield fixer.remove(equalToken);		yield removeSpacesAfter(equalToken, sourceCode, fixer);		for (const currentNode of [node.parent, node]) {			yield * removeParentheses(currentNode, fixer, sourceCode);		}	};}function fixNamedExport(node, sourceCode) {	return function * (fixer) {		const assignmentExpression = node.parent.parent;		const exported = node.parent.property.name;		const local = assignmentExpression.right.name;		yield fixer.replaceText(assignmentExpression, `export {${local} as ${exported}}`);		yield * removeParentheses(assignmentExpression, fixer, sourceCode);	};}function fixExports(node, sourceCode) {	// `exports = bar`	if (isTopLevelAssignment(node)) {		return fixDefaultExport(node, sourceCode);	}	// `exports.foo = bar`	if (isNamedExport(node)) {		return fixNamedExport(node, sourceCode);	}}function fixModuleExports(node, sourceCode) {	if (isModuleExports(node)) {		return fixExports(node.parent, sourceCode);	}}function create(context) {	const filename = context.getFilename().toLowerCase();	if (filename.endsWith('.cjs')) {		return;	}	const sourceCode = context.getSourceCode();	return {		'ExpressionStatement[directive="use strict"]'(node) {			const problem = {node, messageId: ERROR_USE_STRICT_DIRECTIVE};			const fix = function * (fixer) {				yield fixer.remove(node);				yield removeSpacesAfter(node, sourceCode, fixer);			};			if (filename.endsWith('.mjs')) {				problem.fix = fix;			} else {				problem.suggest = [{messageId: SUGGESTION_USE_STRICT_DIRECTIVE, fix}];			}			return problem;		},		'ReturnStatement:not(:function ReturnStatement)'(node) {			return {				node: sourceCode.getFirstToken(node),				messageId: ERROR_GLOBAL_RETURN,			};		},		[identifierSelector](node) {			if (isShadowed(context.getScope(), node)) {				return;			}			const {name} = node;			const problem = {				node,				messageId: ERROR_IDENTIFIER,				data: {name},			};			switch (name) {				case '__filename':				case '__dirname': {					const messageId = node.name === '__dirname' ? SUGGESTION_DIRNAME : SUGGESTION_FILENAME;					const replacement = node.name === '__dirname'						? 'path.dirname(url.fileURLToPath(import.meta.url))'						: 'url.fileURLToPath(import.meta.url)';					problem.suggest = [{						messageId,						fix: fixer => replaceReferenceIdentifier(node, replacement, fixer),					}];					return problem;				}				case 'require': {					const fix = fixRequireCall(node, sourceCode);					if (fix) {						problem.suggest = [{							messageId: SUGGESTION_IMPORT,							fix,						}];						return problem;					}					break;				}				case 'exports': {					const fix = fixExports(node, sourceCode);					if (fix) {						problem.suggest = [{							messageId: SUGGESTION_EXPORT,							fix,						}];						return problem;					}					break;				}				case 'module': {					const fix = fixModuleExports(node, sourceCode);					if (fix) {						problem.suggest = [{							messageId: SUGGESTION_EXPORT,							fix,						}];						return problem;					}					break;				}				default:			}			return problem;		},	};}/** @type {import('eslint').Rule.RuleModule} */module.exports = {	create,	meta: {		type: 'suggestion',		docs: {			description: 'Prefer JavaScript modules (ESM) over CommonJS.',		},		fixable: 'code',		hasSuggestions: true,		messages,	},};
 |