Skip to content

Commit

Permalink
[eslint] avoid hoisting
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Mar 13, 2024
1 parent 70ca58f commit 2efdf79
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 211 deletions.
10 changes: 10 additions & 0 deletions .eslintrc
Expand Up @@ -96,6 +96,7 @@
"no-multiple-empty-lines": [2, { "max": 1, "maxEOF": 1, "maxBOF": 0 }],
"no-return-assign": [2, "always"],
"no-trailing-spaces": 2,
"no-use-before-define": [2, { "functions": true, "classes": true, "variables": true }],
"no-var": 2,
"object-curly-spacing": [2, "always"],
"object-shorthand": ["error", "always", {
Expand Down Expand Up @@ -225,6 +226,15 @@
"no-console": 1,
},
},
{
"files": [
"utils/**", // TODO
"src/ExportMap.js", // TODO
],
"rules": {
"no-use-before-define": "off",
},
},
{
"files": [
"resolvers/*/test/**/*",
Expand Down
49 changes: 24 additions & 25 deletions src/core/importType.js
Expand Up @@ -4,6 +4,11 @@ import isCoreModule from 'is-core-module';
import resolve from 'eslint-module-utils/resolve';
import { getContextPackagePath } from './packagePath';

const scopedRegExp = /^@[^/]+\/?[^/]+/;
export function isScoped(name) {
return name && scopedRegExp.test(name);
}

function baseModule(name) {
if (isScoped(name)) {
const [scope, pkg] = name.split('/');
Expand All @@ -30,20 +35,6 @@ export function isBuiltIn(name, settings, path) {
return isCoreModule(base) || extras.indexOf(base) > -1;
}

export function isExternalModule(name, path, context) {
if (arguments.length < 3) {
throw new TypeError('isExternalModule: name, path, and context are all required');
}
return (isModule(name) || isScoped(name)) && typeTest(name, context, path) === 'external';
}

export function isExternalModuleMain(name, path, context) {
if (arguments.length < 3) {
throw new TypeError('isExternalModule: name, path, and context are all required');
}
return isModuleMain(name) && typeTest(name, context, path) === 'external';
}

const moduleRegExp = /^\w/;
function isModule(name) {
return name && moduleRegExp.test(name);
Expand All @@ -54,20 +45,9 @@ function isModuleMain(name) {
return name && moduleMainRegExp.test(name);
}

const scopedRegExp = /^@[^/]+\/?[^/]+/;
export function isScoped(name) {
return name && scopedRegExp.test(name);
}

const scopedMainRegExp = /^@[^/]+\/?[^/]+$/;
export function isScopedMain(name) {
return name && scopedMainRegExp.test(name);
}

function isRelativeToParent(name) {
return (/^\.\.$|^\.\.[\\/]/).test(name);
}

const indexFiles = ['.', './', './index', './index.js'];
function isIndex(name) {
return indexFiles.indexOf(name) !== -1;
Expand Down Expand Up @@ -123,6 +103,25 @@ function typeTest(name, context, path) {
return 'unknown';
}

export function isExternalModule(name, path, context) {
if (arguments.length < 3) {
throw new TypeError('isExternalModule: name, path, and context are all required');
}
return (isModule(name) || isScoped(name)) && typeTest(name, context, path) === 'external';
}

export function isExternalModuleMain(name, path, context) {
if (arguments.length < 3) {
throw new TypeError('isExternalModule: name, path, and context are all required');
}
return isModuleMain(name) && typeTest(name, context, path) === 'external';
}

const scopedMainRegExp = /^@[^/]+\/?[^/]+$/;
export function isScopedMain(name) {
return name && scopedMainRegExp.test(name);
}

export default function resolveImportType(name, context) {
return typeTest(name, context, resolve(name, context));
}
8 changes: 4 additions & 4 deletions src/core/packagePath.js
Expand Up @@ -2,15 +2,15 @@ import { dirname } from 'path';
import pkgUp from 'eslint-module-utils/pkgUp';
import readPkgUp from 'eslint-module-utils/readPkgUp';

export function getContextPackagePath(context) {
return getFilePackagePath(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename());
}

export function getFilePackagePath(filePath) {
const fp = pkgUp({ cwd: filePath });
return dirname(fp);
}

export function getContextPackagePath(context) {
return getFilePackagePath(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename());
}

export function getFilePackageName(filePath) {
const { pkg, path } = readPkgUp({ cwd: filePath, normalize: false });
if (pkg) {
Expand Down
8 changes: 4 additions & 4 deletions src/rules/no-cycle.js
Expand Up @@ -11,6 +11,10 @@ import docsUrl from '../docsUrl';

const traversed = new Set();

function routeString(route) {
return route.map((s) => `${s.value}:${s.loc.start.line}`).join('=>');
}

module.exports = {
meta: {
type: 'suggestion',
Expand Down Expand Up @@ -151,7 +155,3 @@ module.exports = {
});
},
};

function routeString(route) {
return route.map((s) => `${s.value}:${s.loc.start.line}`).join('=>');
}
160 changes: 80 additions & 80 deletions src/rules/no-duplicates.js
Expand Up @@ -9,28 +9,68 @@ try {
typescriptPkg = require('typescript/package.json'); // eslint-disable-line import/no-extraneous-dependencies
} catch (e) { /**/ }

function checkImports(imported, context) {
for (const [module, nodes] of imported.entries()) {
if (nodes.length > 1) {
const message = `'${module}' imported multiple times.`;
const [first, ...rest] = nodes;
const sourceCode = context.getSourceCode();
const fix = getFix(first, rest, sourceCode, context);
function isPunctuator(node, value) {
return node.type === 'Punctuator' && node.value === value;
}

context.report({
node: first.source,
message,
fix, // Attach the autofix (if any) to the first import.
});
// Get the name of the default import of `node`, if any.
function getDefaultImportName(node) {
const defaultSpecifier = node.specifiers
.find((specifier) => specifier.type === 'ImportDefaultSpecifier');
return defaultSpecifier != null ? defaultSpecifier.local.name : undefined;
}

for (const node of rest) {
context.report({
node: node.source,
message,
});
}
}
}
// Checks whether `node` has a namespace import.
function hasNamespace(node) {
const specifiers = node.specifiers
.filter((specifier) => specifier.type === 'ImportNamespaceSpecifier');
return specifiers.length > 0;
}

// Checks whether `node` has any non-default specifiers.
function hasSpecifiers(node) {
const specifiers = node.specifiers
.filter((specifier) => specifier.type === 'ImportSpecifier');
return specifiers.length > 0;
}

// Checks whether `node` has a comment (that ends) on the previous line or on
// the same line as `node` (starts).
function hasCommentBefore(node, sourceCode) {
return sourceCode.getCommentsBefore(node)
.some((comment) => comment.loc.end.line >= node.loc.start.line - 1);
}

// Checks whether `node` has a comment (that starts) on the same line as `node`
// (ends).
function hasCommentAfter(node, sourceCode) {
return sourceCode.getCommentsAfter(node)
.some((comment) => comment.loc.start.line === node.loc.end.line);
}

// Checks whether `node` has any comments _inside,_ except inside the `{...}`
// part (if any).
function hasCommentInsideNonSpecifiers(node, sourceCode) {
const tokens = sourceCode.getTokens(node);
const openBraceIndex = tokens.findIndex((token) => isPunctuator(token, '{'));
const closeBraceIndex = tokens.findIndex((token) => isPunctuator(token, '}'));
// Slice away the first token, since we're no looking for comments _before_
// `node` (only inside). If there's a `{...}` part, look for comments before
// the `{`, but not before the `}` (hence the `+1`s).
const someTokens = openBraceIndex >= 0 && closeBraceIndex >= 0
? tokens.slice(1, openBraceIndex + 1).concat(tokens.slice(closeBraceIndex + 1))
: tokens.slice(1);
return someTokens.some((token) => sourceCode.getCommentsBefore(token).length > 0);
}

// It's not obvious what the user wants to do with comments associated with
// duplicate imports, so skip imports with comments when autofixing.
function hasProblematicComments(node, sourceCode) {
return (
hasCommentBefore(node, sourceCode)
|| hasCommentAfter(node, sourceCode)
|| hasCommentInsideNonSpecifiers(node, sourceCode)
);
}

function getFix(first, rest, sourceCode, context) {
Expand Down Expand Up @@ -203,68 +243,28 @@ function getFix(first, rest, sourceCode, context) {
};
}

function isPunctuator(node, value) {
return node.type === 'Punctuator' && node.value === value;
}

// Get the name of the default import of `node`, if any.
function getDefaultImportName(node) {
const defaultSpecifier = node.specifiers
.find((specifier) => specifier.type === 'ImportDefaultSpecifier');
return defaultSpecifier != null ? defaultSpecifier.local.name : undefined;
}

// Checks whether `node` has a namespace import.
function hasNamespace(node) {
const specifiers = node.specifiers
.filter((specifier) => specifier.type === 'ImportNamespaceSpecifier');
return specifiers.length > 0;
}

// Checks whether `node` has any non-default specifiers.
function hasSpecifiers(node) {
const specifiers = node.specifiers
.filter((specifier) => specifier.type === 'ImportSpecifier');
return specifiers.length > 0;
}

// It's not obvious what the user wants to do with comments associated with
// duplicate imports, so skip imports with comments when autofixing.
function hasProblematicComments(node, sourceCode) {
return (
hasCommentBefore(node, sourceCode)
|| hasCommentAfter(node, sourceCode)
|| hasCommentInsideNonSpecifiers(node, sourceCode)
);
}

// Checks whether `node` has a comment (that ends) on the previous line or on
// the same line as `node` (starts).
function hasCommentBefore(node, sourceCode) {
return sourceCode.getCommentsBefore(node)
.some((comment) => comment.loc.end.line >= node.loc.start.line - 1);
}
function checkImports(imported, context) {
for (const [module, nodes] of imported.entries()) {
if (nodes.length > 1) {
const message = `'${module}' imported multiple times.`;
const [first, ...rest] = nodes;
const sourceCode = context.getSourceCode();
const fix = getFix(first, rest, sourceCode, context);

// Checks whether `node` has a comment (that starts) on the same line as `node`
// (ends).
function hasCommentAfter(node, sourceCode) {
return sourceCode.getCommentsAfter(node)
.some((comment) => comment.loc.start.line === node.loc.end.line);
}
context.report({
node: first.source,
message,
fix, // Attach the autofix (if any) to the first import.
});

// Checks whether `node` has any comments _inside,_ except inside the `{...}`
// part (if any).
function hasCommentInsideNonSpecifiers(node, sourceCode) {
const tokens = sourceCode.getTokens(node);
const openBraceIndex = tokens.findIndex((token) => isPunctuator(token, '{'));
const closeBraceIndex = tokens.findIndex((token) => isPunctuator(token, '}'));
// Slice away the first token, since we're no looking for comments _before_
// `node` (only inside). If there's a `{...}` part, look for comments before
// the `{`, but not before the `}` (hence the `+1`s).
const someTokens = openBraceIndex >= 0 && closeBraceIndex >= 0
? tokens.slice(1, openBraceIndex + 1).concat(tokens.slice(closeBraceIndex + 1))
: tokens.slice(1);
return someTokens.some((token) => sourceCode.getCommentsBefore(token).length > 0);
for (const node of rest) {
context.report({
node: node.source,
message,
});
}
}
}
}

module.exports = {
Expand Down

0 comments on commit 2efdf79

Please sign in to comment.