Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Mar 17, 2021
1 parent acb2191 commit 6179cbc
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 169 deletions.
211 changes: 85 additions & 126 deletions rules/prefer-module.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,37 @@
"use strict";
const { isParenthesized, isOpeningParenToken } = require("eslint-utils");
const getDocumentationUrl = require("./utils/get-documentation-url");
const isShadowed = require("./utils/is-shadowed");
const removeSpacesAfter = require("./utils/remove-spaces-after");
const isStaticRequire = require("./utils/is-static-require");
const renameIdentifier = require("./utils/rename-identifier");
const getParentheses = require("./utils/get-parentheses");
const assertToken = require("./utils/assert-token");

const ERROR_USE_STRICT_DIRECTIVE = "error/use-strict-directive";
const ERROR_GLOBAL_RETURN = "error/global-return";
const ERROR_REQUIRE = "error/require";
const ERROR_IDENTIFIER = "error/identifier";
const SUGGESTION_DIRNAME = "suggestion/dirname";
const SUGGESTION_FILENAME = "suggestion/filename";
const SUGGESTION_REQUIRE = "suggestion/require";
'use strict';
const {isParenthesized, isOpeningParenToken, ReferenceTracker, READ} = require('eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url');
const isShadowed = require('./utils/is-shadowed');
const removeSpacesAfter = require('./utils/remove-spaces-after');
const isStaticRequire = require('./utils/is-static-require');
const renameIdentifier = require('./utils/rename-identifier');
const getParentheses = require('./utils/get-parentheses');
const assertToken = require('./utils/assert-token');

const ERROR_USE_STRICT_DIRECTIVE = 'error/use-strict-directive';
const ERROR_GLOBAL_RETURN = 'error/global-return';
const ERROR_REQUIRE = 'error/require';
const ERROR_IDENTIFIER = 'error/identifier';
const SUGGESTION_DIRNAME = 'suggestion/dirname';
const SUGGESTION_FILENAME = 'suggestion/filename';
const SUGGESTION_REQUIRE = 'suggestion/require';
const messages = {
[ERROR_USE_STRICT_DIRECTIVE]: 'Do not use "use strict" directive.',
[ERROR_GLOBAL_RETURN]: '"return" should used inside a function.',
[ERROR_IDENTIFIER]: 'Do not use "{{name}}".',
[ERROR_REQUIRE]: "Use `import` instead of `require`.",
[ERROR_REQUIRE]: 'Use `import` instead of `require`.',
[SUGGESTION_DIRNAME]: 'Replace "__dirname" with `"…(import.meta.url)"`.',
[SUGGESTION_FILENAME]: 'Replace "__filename" with `"…(import.meta.url)"`.',
[SUGGESTION_REQUIRE]: "Switch to `import`.",
[SUGGESTION_REQUIRE]: 'Switch to `import`.'
};

// TODO: DRY this and `propertiesSelector` in `./prefer-number-properties.js`
const identifierSelector = [
"Identifier",
`:matches(${["__dirname", "__filename", "exports", "module"]
.map((name) => `[name="${name}"]`)
.join(", ")})`,
`:not(${[
"MemberExpression[computed=false] > .property",
"FunctionDeclaration > .id",
"ImportSpecifier > .imported",
"ExportSpecifier > .exported",
"ClassDeclaration > .id",
"ClassProperty[computed=false] > .key",
"MethodDefinition[computed=false] > .key",
"VariableDeclarator > .id",
"Property[shorthand=false][computed=false] > .key",
"TSDeclareFunction > .id",
"TSEnumMember > .id",
"TSPropertySignature > .key",
].join(", ")})`,
].join("");

const requireCallSelector = [
"CallExpression",
'CallExpression',
'[callee.type="Identifier"]',
'[callee.name="require"]',
"[arguments.length=1]",
'[arguments.length=1]'
];

// const is

function fixImport(requireCall, sourceCode) {
if (!isStaticRequire(requireCall)) {
return;
Expand All @@ -64,17 +40,18 @@ function fixImport(requireCall, sourceCode) {
const {
parent,
callee,
arguments: [source],
arguments: [source]
} = requireCall;
if (parent.type === "ExpressionStatement") {
return function* (fixer) {

if (parent.type === 'ExpressionStatement') {
return function * (fixer) {
// Remove parens around source and call
yield fixer.replaceText(callee, "import");
yield fixer.replaceText(callee, 'import');
const openingParenthesisToken = sourceCode.getTokenAfter(
callee,
isOpeningParenToken
);
yield fixer.replaceText(openingParenthesisToken, " ");
yield fixer.replaceText(openingParenthesisToken, ' ');
const closingParenthesisToken = sourceCode.getLastToken(requireCall);
yield fixer.remove(closingParenthesisToken);

Expand All @@ -88,45 +65,45 @@ function fixImport(requireCall, sourceCode) {
}

if (
parent.type === "VariableDeclarator" &&
parent.type === 'VariableDeclarator' &&
parent.init === requireCall &&
parent.parent.type === "VariableDeclaration" &&
parent.parent.kind === "const" &&
parent.parent.type === 'VariableDeclaration' &&
parent.parent.kind === 'const' &&
parent.parent.declarations.length == 1 &&
parent.parent.declarations[0] === parent
) {
const declarator = parent;
const declaration = declarator.parent;
const { id } = declarator;
const {id} = declarator;

if (
id.type === "Identifier" ||
(id.type === "ObjectPattern" &&
id.type === 'Identifier' ||
(id.type === 'ObjectPattern' &&
id.properties.length > 0 &&
id.properties.every(({ key, value, computed }) => {
id.properties.every(({key, value, computed}) => {
return (
!computed &&
value.type === "Identifier" &&
key.type === "Identifier"
value.type === 'Identifier' &&
key.type === 'Identifier'
);
}))
) {
return function* (fixer) {
return function * (fixer) {
const constToken = sourceCode.getFirstToken(declaration);
assertToken(constToken, {
expected: { type: "Keyword", value: "const" },
ruleId: "prefer-module",
expected: {type: 'Keyword', value: 'const'},
ruleId: 'prefer-module'
});
yield fixer.replaceText(constToken, "import");
yield fixer.replaceText(constToken, 'import');

const equalToken = sourceCode.getTokenAfter(id);
assertToken(equalToken, {
expected: { type: "Punctuator", value: "=" },
ruleId: "prefer-module",
expected: {type: 'Punctuator', value: '='},
ruleId: 'prefer-module'
});
yield removeSpacesAfter(id, sourceCode, fixer);
yield removeSpacesAfter(equalToken, sourceCode, fixer);
yield fixer.replaceText(equalToken, " from ");
yield fixer.replaceText(equalToken, ' from ');

yield fixer.remove(callee);
const openingParenthesisToken = sourceCode.getTokenAfter(
Expand All @@ -144,23 +121,23 @@ function fixImport(requireCall, sourceCode) {
}
}

if (id.type === "Identifier") {
if (id.type === 'Identifier') {
return;
}

const { properties } = id;
const {properties} = id;

for (const property of properties) {
const { key, value, shorthand } = property;
const {key, value, shorthand} = property;
if (!shorthand) {
const commaToken = sourceCode.getTokenAfter(key);
assertToken(commaToken, {
expected: { type: "Punctuator", value: ":" },
ruleId: "prefer-module",
expected: {type: 'Punctuator', value: ':'},
ruleId: 'prefer-module'
});
yield removeSpacesAfter(key, sourceCode, fixer);
yield removeSpacesAfter(commaToken, sourceCode, fixer);
yield fixer.replaceText(commaToken, " as ");
yield fixer.replaceText(commaToken, ' as ');
}
}
};
Expand All @@ -169,9 +146,9 @@ function fixImport(requireCall, sourceCode) {
}

function getExportSuggestions() {
// module.export = {} -> export default
// module.export.x = {} -> export const x = {}
// module.export.x = b -> export {b as x} // Maybe not, TODO
// module.exports = {} -> export default
// module.exports.x = {} -> export const x = {}
// module.exports.x = b -> export {b as x} // Maybe not, TODO
// exports = {} -> export default
// exports.x = {} -> export const x = {}
}
Expand All @@ -184,16 +161,16 @@ function create(context) {
context.report({
node,
messageId: ERROR_USE_STRICT_DIRECTIVE,
*fix(fixer) {
* fix(fixer) {
yield fixer.remove(node);
yield removeSpacesAfter(node, sourceCode, fixer);
},
}
});
},
"ReturnStatement:not(:function ReturnStatement)"(node) {
'ReturnStatement:not(:function ReturnStatement)'(node) {
context.report({
node: sourceCode.getFirstToken(node),
messageId: ERROR_GLOBAL_RETURN,
messageId: ERROR_GLOBAL_RETURN
});
},
[requireCallSelector](node) {
Expand All @@ -203,69 +180,51 @@ function create(context) {

const problem = {
node,
messageId: ERROR_REQUIRE,
messageId: ERROR_REQUIRE
};

const fix = fixImport(node, sourceCode);
if (fix) {
problem.suggest = [
{
messageId: SUGGESTION_REQUIRE,
fix: fixImport(node, sourceCode),
},
fix: fixImport(node, sourceCode)
}
];
}

context.report(problem);
},
[identifierSelector](node) {
if (isShadowed(context.getScope(), node)) {
return;
}

const { name } = node;
const problem = {
node,
messageId: ERROR_IDENTIFIER,
data: { name },
Program() {
const tracker = new ReferenceTracker(context.getScope());
const trackMap = {
__dirname: { [READ]: true },
__filename: { [READ]: true }
};

switch (name) {
case "__dirname":
problem.suggest = [
{
messageId: SUGGESTION_DIRNAME,
fix: (fixer) =>
renameIdentifier(
node,
"path.dirname(url.fileURLToPath(import.meta.url))",
fixer,
sourceCode
),
},
];
break;
case "__filename":
problem.suggest = [
for (const {node} of tracker.iterateGlobalReferences(trackMap)) {
const replacement = node.name === '__dirname' ?
'path.dirname(url.fileURLToPath(import.meta.url))' :
'url.fileURLToPath(import.meta.url)';

context.report({
node,
messageId: ERROR_IDENTIFIER,
data: {name},
suggest: [
{
messageId: SUGGESTION_FILENAME,
fix: (fixer) =>
fix: fixer =>
renameIdentifier(
node,
"url.fileURLToPath(import.meta.url)",
replacement,
fixer,
sourceCode
),
},
];
break;
case "exports":
case "module":
problem.suggest = getExportSuggestions;
break;
)
}
]
});
}

context.report(problem);
},
};
}
Expand All @@ -275,12 +234,12 @@ const schema = [];
module.exports = {
create,
meta: {
type: "suggestion",
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename),
url: getDocumentationUrl(__filename)
},
fixable: "code",
fixable: 'code',
schema,
messages,
},
messages
}
};
14 changes: 7 additions & 7 deletions test/prefer-module.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import outdent from "outdent";
import { getTester } from "./utils/test.mjs";
import outdent from 'outdent';
import {getTester} from './utils/test.mjs';

const { test } = getTester(import.meta);
const {test} = getTester(import.meta);

// // Strict mode
// test.snapshot({
Expand Down Expand Up @@ -94,7 +94,7 @@ test.snapshot({
'new require("foo")',
'require("foo", extraArgument)',
'require(..."foo")',
"const a = require()",
'const a = require()',
'const foo = require.resolve("foo");',
outdent`
import {createRequire} from 'module';
Expand Down Expand Up @@ -172,13 +172,13 @@ test.snapshot({
'const {a :foo, b: bar, default : baz} = (( require("foo") ));',
// Not fixable
'require("../" + "file.js")',
"require(file)",
'require(file)',
'a = require("foo")',
'function a(a = require("foo")) {}',
'let foo = require("foo");',
'const foo = require("foo"), bar = require("bar");',
'const {} = require("foo");',
'const {[foo]: foo} = require("foo");',
'const {["foo"]: foo} = require("foo");',
],
'const {["foo"]: foo} = require("foo");'
]
});
Loading

0 comments on commit 6179cbc

Please sign in to comment.