拿掉 build files

This commit is contained in:
2022-07-18 16:33:23 +08:00
parent 41e2287bcb
commit 3707f158a3
31953 changed files with 0 additions and 4411796 deletions
Binary file not shown.
-104
View File
@@ -1,104 +0,0 @@
'use strict';
const cleanRegexp = require('clean-regexp');
const {optimize} = require('regexp-tree');
const getDocumentationUrl = require('./utils/get-documentation-url');
const quoteString = require('./utils/quote-string');
const message = '{{original}} can be optimized to {{optimized}}';
const create = context => {
const {sortCharacterClasses} = context.options[0] || {};
const blacklist = [];
if (sortCharacterClasses === false) {
blacklist.push('charClassClassrangesMerge');
}
return {
'Literal[regex]': node => {
const {raw: original, regex} = node;
// Regular Expressions with `u` flag are not well handled by `regexp-tree`
// https://github.com/DmitrySoshnikov/regexp-tree/issues/162
if (regex.flags.includes('u')) {
return;
}
let optimized = original;
try {
optimized = optimize(original, undefined, {blacklist}).toString();
} catch (_) {}
if (original === optimized) {
return;
}
context.report({
node,
message,
data: {
original,
optimized
},
fix: fixer => fixer.replaceText(node, optimized)
});
},
'NewExpression[callee.type="Identifier"][callee.name="RegExp"][arguments.length>=1][arguments.0.type="Literal"]': node => {
const [patternNode, flagsNode] = node.arguments;
if (typeof patternNode.value !== 'string') {
return;
}
const oldPattern = patternNode.value;
const flags = flagsNode &&
flagsNode.type === 'Literal' &&
typeof flagsNode.value === 'string' ?
flagsNode.value :
'';
const newPattern = cleanRegexp(oldPattern, flags);
if (oldPattern !== newPattern) {
context.report({
node,
message,
data: {
original: oldPattern,
optimized: newPattern
},
fix: fixer => fixer.replaceText(
patternNode,
quoteString(newPattern)
)
});
}
}
};
};
const schema = [
{
type: 'object',
properties: {
sortCharacterClasses: {
type: 'boolean',
default: true
}
}
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema
}
};
-131
View File
@@ -1,131 +0,0 @@
'use strict';
const {findVariable} = require('eslint-utils');
const avoidCapture = require('./utils/avoid-capture');
const getDocumentationUrl = require('./utils/get-documentation-url');
const renameVariable = require('./utils/rename-variable');
const methodSelector = require('./utils/method-selector');
const ERROR_MESSAGE_ID = 'error';
const promiseMethodSelector = (method, argumentsLength, argumentIndex) => [
methodSelector({
name: method,
length: argumentsLength
}),
`:matches(${
[
'FunctionExpression',
'ArrowFunctionExpression'
].map(type => `[arguments.${argumentIndex}.type="${type}"]`).join(', ')
})`,
`[arguments.${argumentIndex}.params.length=1]`,
`[arguments.${argumentIndex}.params.0.type="Identifier"]`
].join('');
// Matches `promise.catch([FunctionExpression | ArrowFunctionExpression])`
const promiseCatchSelector = promiseMethodSelector('catch', 1, 0);
// Matches `promise.then(any, [FunctionExpression | ArrowFunctionExpression])`
const promiseThenSelector = promiseMethodSelector('then', 2, 1);
const catchSelector = [
'CatchClause',
'>',
'Identifier.param'
].join('');
const create = context => {
const {ecmaVersion} = context.parserOptions;
const sourceCode = context.getSourceCode();
const options = {
name: 'error',
ignore: [],
...context.options[0]
};
const {name: expectedName} = options;
const ignore = options.ignore.map(
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u')
);
const isNameAllowed = name =>
name === expectedName ||
ignore.some(regexp => regexp.test(name)) ||
name.endsWith(expectedName) ||
name.endsWith(expectedName.charAt(0).toUpperCase() + expectedName.slice(1));
function check(node) {
const originalName = node.name;
if (
isNameAllowed(originalName) ||
isNameAllowed(originalName.replace(/_+$/g, ''))
) {
return;
}
const scope = context.getScope();
const variable = findVariable(scope, node);
if (originalName === '_' && variable.references.length === 0) {
return;
}
const scopes = [
variable.scope,
...variable.references.map(({from}) => from)
];
const fixedName = avoidCapture(expectedName, scopes, ecmaVersion);
context.report({
node,
messageId: ERROR_MESSAGE_ID,
data: {
originalName,
fixedName
},
fix: fixer => renameVariable(variable, fixedName, fixer, sourceCode)
});
}
return {
[promiseCatchSelector]: node => {
check(node.arguments[0].params[0]);
},
[promiseThenSelector]: node => {
check(node.arguments[1].params[0]);
},
[catchSelector]: node => {
check(node);
}
};
};
const schema = [
{
type: 'object',
properties: {
name: {
type: 'string'
},
ignore: {
type: 'array',
uniqueItems: true
}
}
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema,
messages: {
[ERROR_MESSAGE_ID]: 'The catch parameter `{{originalName}}` should be named `{{fixedName}}`.'
}
}
};
-195
View File
@@ -1,195 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const getReferences = require('./utils/get-references');
const MESSAGE_ID_NAMED = 'named';
const MESSAGE_ID_ANONYMOUS = 'anonymous';
const isSameScope = (scope1, scope2) =>
scope1 && scope2 && (scope1 === scope2 || scope1.block === scope2.block);
function checkReferences(scope, parent, scopeManager) {
const hitReference = references => references.some(reference => {
if (isSameScope(parent, reference.from)) {
return true;
}
const {resolved} = reference;
const [definition] = resolved.defs;
// Skip recursive function name
if (definition && definition.type === 'FunctionName' && resolved.name === definition.name.name) {
return false;
}
return isSameScope(parent, resolved.scope);
});
const hitDefinitions = definitions => definitions.some(definition => {
const scope = scopeManager.acquire(definition.node);
return isSameScope(parent, scope);
});
// This check looks for neighboring function definitions
const hitIdentifier = identifiers => identifiers.some(identifier => {
// Only look at identifiers that live in a FunctionDeclaration
if (
!identifier.parent ||
identifier.parent.type !== 'FunctionDeclaration'
) {
return false;
}
const identifierScope = scopeManager.acquire(identifier);
// If we have a scope, the earlier checks should have worked so ignore them here
if (identifierScope) {
return false;
}
const identifierParentScope = scopeManager.acquire(identifier.parent);
if (!identifierParentScope) {
return false;
}
// Ignore identifiers from our own scope
if (isSameScope(scope, identifierParentScope)) {
return false;
}
// Look at the scope above the function definition to see if lives
// next to the reference being checked
return isSameScope(parent, identifierParentScope.upper);
});
return getReferences(scope)
.map(({resolved}) => resolved)
.filter(Boolean)
.some(variable =>
hitReference(variable.references) ||
hitDefinitions(variable.defs) ||
hitIdentifier(variable.identifiers)
);
}
// https://reactjs.org/docs/hooks-reference.html
const reactHooks = new Set([
'useState',
'useEffect',
'useContext',
'useReducer',
'useCallback',
'useMemo',
'useRef',
'useImperativeHandle',
'useLayoutEffect',
'useDebugValue'
]);
const isReactHook = scope =>
scope.block &&
scope.block.parent &&
scope.block.parent.callee &&
scope.block.parent.callee.type === 'Identifier' &&
reactHooks.has(scope.block.parent.callee.name);
const isArrowFunctionWithThis = scope =>
scope.type === 'function' &&
scope.block &&
scope.block.type === 'ArrowFunctionExpression' &&
(scope.thisFound || scope.childScopes.some(scope => isArrowFunctionWithThis(scope)));
function checkNode(node, scopeManager) {
const scope = scopeManager.acquire(node);
if (!scope || isArrowFunctionWithThis(scope)) {
return true;
}
let parentNode = node.parent;
if (!parentNode) {
return true;
}
// Skip over junk like the block statement inside of a function declaration
// or the various pieces of an arrow function.
if (parentNode.type === 'VariableDeclarator') {
parentNode = parentNode.parent;
}
if (parentNode.type === 'VariableDeclaration') {
parentNode = parentNode.parent;
}
if (parentNode.type === 'BlockStatement') {
parentNode = parentNode.parent;
}
const parentScope = scopeManager.acquire(parentNode);
if (!parentScope || parentScope.type === 'global' || isReactHook(parentScope)) {
return true;
}
return checkReferences(scope, parentScope, scopeManager);
}
const create = context => {
const sourceCode = context.getSourceCode();
const {scopeManager} = sourceCode;
const functions = [];
let hasJsx = false;
return {
'ArrowFunctionExpression, FunctionDeclaration': node => functions.push(node),
JSXElement: () => {
// Turn off this rule if we see a JSX element because scope
// references does not include JSXElement nodes.
hasJsx = true;
},
':matches(ArrowFunctionExpression, FunctionDeclaration):exit': node => {
if (!hasJsx && !checkNode(node, scopeManager)) {
const functionType = node.type === 'ArrowFunctionExpression' ? 'arrow function' : 'function';
let functionName = '';
if (node.id) {
functionName = node.id.name;
} else if (
node.parent &&
node.parent.type === 'VariableDeclarator' &&
node.parent.id &&
node.parent.id.type === 'Identifier'
) {
functionName = node.parent.id.name;
}
context.report({
node,
messageId: functionName ? MESSAGE_ID_NAMED : MESSAGE_ID_ANONYMOUS,
data: {
functionType,
functionName
}
});
}
functions.pop();
if (functions.length === 0) {
hasJsx = false;
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
messages: {
[MESSAGE_ID_NAMED]: 'Move {{functionType}} `{{functionName}}` to the outer scope.',
[MESSAGE_ID_ANONYMOUS]: 'Move {{functionType}} to the outer scope.'
}
}
};
-225
View File
@@ -1,225 +0,0 @@
'use strict';
const {upperFirst} = require('lodash');
const getDocumentationUrl = require('./utils/get-documentation-url');
const MESSAGE_ID_INVALID_EXPORT = 'invalidExport';
const nameRegexp = /^(?:[A-Z][\da-z]*)*Error$/;
const getClassName = name => upperFirst(name).replace(/(?:error|)$/i, 'Error');
const getConstructorMethod = className => `
constructor() {
super();
this.name = '${className}';
}
`;
const hasValidSuperClass = node => {
if (!node.superClass) {
return false;
}
let {name} = node.superClass;
if (node.superClass.type === 'MemberExpression') {
({name} = node.superClass.property);
}
return nameRegexp.test(name);
};
const isSuperExpression = node =>
node.type === 'ExpressionStatement' &&
node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'Super';
const isAssignmentExpression = (node, name) => {
if (
node.type !== 'ExpressionStatement' ||
node.expression.type !== 'AssignmentExpression'
) {
return false;
}
const lhs = node.expression.left;
if (!lhs.object || lhs.object.type !== 'ThisExpression') {
return false;
}
return lhs.property.name === name;
};
const isClassProperty = (node, name) => {
if (node.type !== 'ClassProperty' || node.computed) {
return false;
}
const {key} = node;
if (key.type !== 'Identifier') {
return false;
}
return key.name === name;
};
const customErrorDefinition = (context, node) => {
if (!hasValidSuperClass(node)) {
return;
}
if (node.id === null) {
return;
}
const {name} = node.id;
const className = getClassName(name);
if (name !== className) {
context.report({
node: node.id,
message: `Invalid class name, use \`${className}\`.`
});
}
const {body} = node.body;
const constructor = body.find(x => x.kind === 'constructor');
if (!constructor) {
context.report({
node,
message: 'Add a constructor to your error.',
fix: fixer => fixer.insertTextAfterRange([
node.body.range[0],
node.body.range[0] + 1
], getConstructorMethod(name))
});
return;
}
const constructorBodyNode = constructor.value.body;
// Verify the constructor has a body (TypeScript)
if (!constructorBodyNode) {
return;
}
const constructorBody = constructorBodyNode.body;
const superExpression = constructorBody.find(body => isSuperExpression(body));
const messageExpressionIndex = constructorBody.findIndex(x => isAssignmentExpression(x, 'message'));
if (!superExpression) {
context.report({
node: constructorBodyNode,
message: 'Missing call to `super()` in constructor.'
});
} else if (messageExpressionIndex !== -1) {
const expression = constructorBody[messageExpressionIndex];
context.report({
node: superExpression,
message: 'Pass the error message to `super()` instead of setting `this.message`.',
fix: fixer => {
const fixings = [];
if (superExpression.expression.arguments.length === 0) {
const rhs = expression.expression.right;
fixings.push(
fixer.insertTextAfterRange([
superExpression.range[0],
superExpression.range[0] + 6
], rhs.raw || rhs.name)
);
}
fixings.push(
fixer.removeRange([
messageExpressionIndex === 0 ? constructorBodyNode.range[0] : constructorBody[messageExpressionIndex - 1].range[1],
expression.range[1]
])
);
return fixings;
}
});
}
const nameExpression = constructorBody.find(x => isAssignmentExpression(x, 'name'));
if (!nameExpression) {
const nameProperty = node.body.body.find(node => isClassProperty(node, 'name'));
if (!nameProperty || !nameProperty.value || nameProperty.value.value !== name) {
context.report({
node: nameProperty && nameProperty.value ? nameProperty.value : constructorBodyNode,
message: `The \`name\` property should be set to \`${name}\`.`
});
}
} else if (nameExpression.expression.right.value !== name) {
context.report({
node: nameExpression ? nameExpression.expression.right : constructorBodyNode,
message: `The \`name\` property should be set to \`${name}\`.`
});
}
};
const customErrorExport = (context, node) => {
if (!node.left.object || node.left.object.name !== 'exports') {
return;
}
if (!node.left.property) {
return;
}
const exportsName = node.left.property.name;
const maybeError = node.right;
if (maybeError.type !== 'ClassExpression') {
return;
}
if (!hasValidSuperClass(maybeError)) {
return;
}
if (!maybeError.id) {
return;
}
// Assume rule has already fixed the error name
const errorName = maybeError.id.name;
if (exportsName === errorName) {
return;
}
context.report({
node: node.left.property,
messageId: MESSAGE_ID_INVALID_EXPORT,
fix: fixer => fixer.replaceText(node.left.property, errorName)
});
};
const create = context => {
return {
ClassDeclaration: node => customErrorDefinition(context, node),
'AssignmentExpression[right.type="ClassExpression"]': node => customErrorDefinition(context, node.right),
'AssignmentExpression[left.type="MemberExpression"]': node => customErrorExport(context, node)
};
};
module.exports = {
create,
meta: {
type: 'problem',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages: {
[MESSAGE_ID_INVALID_EXPORT]: 'Exported error name should match error class'
}
}
};
-104
View File
@@ -1,104 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const errorConstructors = new Set([
'Error',
'EvalError',
'InternalError',
'RangeError',
'ReferenceError',
'SyntaxError',
'TypeError',
'URIError'
]);
const isReferenceAssigned = expression => {
if (expression.type === 'AssignmentExpression') {
const assignedVariable = expression.left;
return assignedVariable.type === 'Identifier' && assignedVariable.name;
}
return false;
};
const findIdentifierValues = (identifierNode, context) => {
const scope = context.getScope(identifierNode);
const declarations = scope.set.get(identifierNode.name);
if (declarations === undefined) {
return [];
}
const expressions = declarations.references.map(reference => reference.identifier.parent);
const referenceValues = [];
for (const expression of expressions) {
if (isReferenceAssigned(expression)) {
referenceValues.push(expression.right);
} else if (expression.type === 'VariableDeclarator') {
referenceValues.push(expression.init);
}
}
return referenceValues;
};
const isEmptyMessageString = node => {
return (
node.arguments.length > 0 &&
node.arguments[0].type === 'Literal' &&
!node.arguments[0].value
);
};
const reportError = (expressionNode, context) => {
const error = expressionNode.callee;
if (errorConstructors.has(error.name)) {
if (expressionNode.arguments.length === 0) {
context.report({
node: expressionNode.parent,
message: 'Pass a message to the error constructor.'
});
}
if (isEmptyMessageString(expressionNode)) {
context.report({
node: expressionNode.parent,
message: 'Error message should not be an empty string.'
});
}
}
};
const checkErrorMessage = (node, context) => {
if (node.type === 'Identifier') {
const identifierValues = findIdentifierValues(node, context);
for (const node of identifierValues) {
checkErrorMessage(node, context);
}
} else if (node.type === 'NewExpression' || node.type === 'CallExpression') {
reportError(node, context);
}
};
const create = context => {
const throwStatements = [];
return {
'ThrowStatement'(throwStatement) {
throwStatements.push(throwStatement);
},
'Program:exit'() {
for (const throwStatement of throwStatements) {
checkErrorMessage(throwStatement.argument, context);
}
}
};
};
module.exports = {
create,
meta: {
type: 'problem',
docs: {
url: getDocumentationUrl(__filename)
}
}
};
-59
View File
@@ -1,59 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const replaceTemplateElement = require('./utils/replace-template-element');
const escapeWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+})/g;
const escapePatternWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+}|c[a-z])/g;
const message = 'Use uppercase characters for the value of the escape sequence.';
const create = context => {
const check = ({node, original, regex = escapeWithLowercase, fix}) => {
const fixed = original.replace(regex, data => data.slice(0, 1) + data.slice(1).toUpperCase());
if (fixed !== original) {
context.report({
node,
message,
fix: fixer => fix ? fix(fixer, fixed) : fixer.replaceText(node, fixed)
});
}
};
return {
Literal(node) {
if (typeof node.value !== 'string') {
return;
}
check({
node,
original: node.raw
});
},
'Literal[regex]'(node) {
check({
node,
original: node.raw,
regex: escapePatternWithLowercase
});
},
TemplateElement(node) {
check({
node,
original: node.value.raw,
fix: (fixer, fixed) => replaceTemplateElement(fixer, node, fixed)
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-533
View File
@@ -1,533 +0,0 @@
'use strict';
const readPkgUp = require('read-pkg-up');
const semver = require('semver');
const ci = require('ci-info');
const baseRule = require('eslint/lib/rules/no-warning-comments');
const getDocumentationUrl = require('./utils/get-documentation-url');
// `unicorn/` prefix is added to avoid conflicts with core rule
const MESSAGE_ID_AVOID_MULTIPLE_DATES = 'unicorn/avoidMultipleDates';
const MESSAGE_ID_EXPIRED_TODO = 'unicorn/expiredTodo';
const MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS =
'unicorn/avoidMultiplePackageVersions';
const MESSAGE_ID_REACHED_PACKAGE_VERSION = 'unicorn/reachedPackageVersion';
const MESSAGE_ID_HAVE_PACKAGE = 'unicorn/havePackage';
const MESSAGE_ID_DONT_HAVE_PACKAGE = 'unicorn/dontHavePackage';
const MESSAGE_ID_VERSION_MATCHES = 'unicorn/versionMatches';
const MESSAGE_ID_ENGINE_MATCHES = 'unicorn/engineMatches';
const MESSAGE_ID_REMOVE_WHITESPACES = 'unicorn/removeWhitespaces';
const MESSAGE_ID_MISSING_AT_SYMBOL = 'unicorn/missingAtSymbol';
const packageResult = readPkgUp.sync();
const hasPackage = Boolean(packageResult);
const packageJson = hasPackage ? packageResult.packageJson : {};
const packageDependencies = {
...packageJson.dependencies,
...packageJson.devDependencies
};
const DEPENDENCY_INCLUSION_RE = /^[+-]\s*@?\S+\/?\S+/;
const VERSION_COMPARISON_RE = /^(?<name>@?\S\/?\S+)@(?<condition>>|>=)(?<version>\d+(?:\.\d+){0,2}(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)/i;
const PKG_VERSION_RE = /^(?<condition>>|>=)(?<version>\d+(?:\.\d+){0,2}(?:-[\da-z-]+(?:\.[\da-z-]+)*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?)\s*$/;
const ISO8601_DATE = /\d{4}-\d{2}-\d{2}/;
function parseTodoWithArguments(string, {terms}) {
const lowerCaseString = string.toLowerCase();
const lowerCaseTerms = terms.map(term => term.toLowerCase());
const hasTerm = lowerCaseTerms.some(term => lowerCaseString.includes(term));
if (!hasTerm) {
return false;
}
const TODO_ARGUMENT_RE = /\[(?<rawArguments>[^}]+)]/i;
const result = TODO_ARGUMENT_RE.exec(string);
if (!result) {
return false;
}
const {rawArguments} = result.groups;
return rawArguments
.split(',')
.map(argument => parseArgument(argument.trim()))
.reduce((groups, argument) => {
if (!groups[argument.type]) {
groups[argument.type] = [];
}
groups[argument.type].push(argument.value);
return groups;
}, {});
}
function parseArgument(argumentString) {
if (ISO8601_DATE.test(argumentString)) {
return {
type: 'dates',
value: argumentString
};
}
if (hasPackage && DEPENDENCY_INCLUSION_RE.test(argumentString)) {
const condition = argumentString[0] === '+' ? 'in' : 'out';
const name = argumentString.slice(1).trim();
return {
type: 'dependencies',
value: {
name,
condition
}
};
}
if (hasPackage && VERSION_COMPARISON_RE.test(argumentString)) {
const {groups} = VERSION_COMPARISON_RE.exec(argumentString);
const name = groups.name.trim();
const condition = groups.condition.trim();
const version = groups.version.trim();
const hasEngineKeyword = name.indexOf('engine:') === 0;
const isNodeEngine = hasEngineKeyword && name === 'engine:node';
if (hasEngineKeyword && isNodeEngine) {
return {
type: 'engines',
value: {
condition,
version
}
};
}
if (!hasEngineKeyword) {
return {
type: 'dependencies',
value: {
name,
condition,
version
}
};
}
}
if (hasPackage && PKG_VERSION_RE.test(argumentString)) {
const result = PKG_VERSION_RE.exec(argumentString);
const {condition, version} = result.groups;
return {
type: 'packageVersions',
value: {
condition: condition.trim(),
version: version.trim()
}
};
}
// Currently being ignored as integration tests pointed
// some TODO comments have `[random data like this]`
return {
type: 'unknowns',
value: argumentString
};
}
function parseTodoMessage(todoString) {
// @example "TODO [...]: message here"
// @example "TODO [...] message here"
const argumentsEnd = todoString.indexOf(']');
const afterArguments = todoString.slice(argumentsEnd + 1).trim();
// Check if have to skip colon
// @example "TODO [...]: message here"
const dropColon = afterArguments[0] === ':';
if (dropColon) {
return afterArguments.slice(1).trim();
}
return afterArguments;
}
function reachedDate(past) {
const now = new Date().toISOString().slice(0, 10);
return Date.parse(past) < Date.parse(now);
}
function tryToCoerceVersion(rawVersion) {
if (!rawVersion) {
return false;
}
let version = String(rawVersion);
// Remove leading things like `^1.0.0`, `>1.0.0`
const leadingNoises = [
'>=',
'<=',
'>',
'<',
'~',
'^'
];
const foundTrailingNoise = leadingNoises.find(noise => version.startsWith(noise));
if (foundTrailingNoise) {
version = version.slice(foundTrailingNoise.length);
}
// Get only the first member for cases such as `1.0.0 - 2.9999.9999`
const parts = version.split(' ');
if (parts.length > 1) {
version = parts[0];
}
if (semver.valid(version)) {
return version;
}
try {
// Try to semver.parse a perfect match while semver.coerce tries to fix errors
// But coerce can't parse pre-releases.
return semver.parse(version) || semver.coerce(version);
} catch (_) {
return false;
}
}
function semverComparisonForOperator(operator) {
return {
'>': semver.gt,
'>=': semver.gte
}[operator];
}
const create = context => {
const options = {
terms: ['todo', 'fixme', 'xxx'],
ignore: [],
ignoreDatesOnPullRequests: true,
allowWarningComments: true,
...context.options[0]
};
const ignoreRegexes = options.ignore.map(
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u')
);
const sourceCode = context.getSourceCode();
const comments = sourceCode.getAllComments();
const unusedComments = comments
.filter(token => token.type !== 'Shebang')
// Block comments come as one.
// Split for situations like this:
// /*
// * TODO [2999-01-01]: Validate this
// * TODO [2999-01-01]: And this
// * TODO [2999-01-01]: Also this
// */
.map(comment =>
comment.value.split('\n').map(line => ({
...comment,
value: line
}))
)
// Flatten
.reduce((accumulator, array) => accumulator.concat(array), [])
.filter(comment => processComment(comment));
// This is highly dependable on ESLint's `no-warning-comments` implementation.
// What we do is patch the parts we know the rule will use, `getAllComments`.
// Since we have priority, we leave only the comments that we didn't use.
const fakeContext = {
...context,
getSourceCode() {
return {
...sourceCode,
getAllComments() {
return options.allowWarningComments ? [] : unusedComments;
}
};
}
};
const rules = baseRule.create(fakeContext);
function processComment(comment) {
if (ignoreRegexes.some(ignore => ignore.test(comment.value))) {
return;
}
const parsed = parseTodoWithArguments(comment.value, options);
if (!parsed) {
return true;
}
// Count if there are valid properties.
// Otherwise, it's a useless TODO and falls back to `no-warning-comments`.
let uses = 0;
const {
packageVersions = [],
dates = [],
dependencies = [],
engines = [],
unknowns = []
} = parsed;
if (dates.length > 1) {
uses++;
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_AVOID_MULTIPLE_DATES,
data: {
expirationDates: dates.join(', '),
message: parseTodoMessage(comment.value)
}
});
} else if (dates.length === 1) {
uses++;
const [date] = dates;
const shouldIgnore = options.ignoreDatesOnPullRequests && ci.isPR;
if (!shouldIgnore && reachedDate(date)) {
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_EXPIRED_TODO,
data: {
expirationDate: date,
message: parseTodoMessage(comment.value)
}
});
}
}
if (packageVersions.length > 1) {
uses++;
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS,
data: {
versions: packageVersions
.map(({condition, version}) => `${condition}${version}`)
.join(', '),
message: parseTodoMessage(comment.value)
}
});
} else if (packageVersions.length === 1) {
uses++;
const [{condition, version}] = packageVersions;
const packageVersion = tryToCoerceVersion(packageJson.version);
const decidedPackageVersion = tryToCoerceVersion(version);
const compare = semverComparisonForOperator(condition);
if (packageVersion && compare(packageVersion, decidedPackageVersion)) {
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_REACHED_PACKAGE_VERSION,
data: {
comparison: `${condition}${version}`,
message: parseTodoMessage(comment.value)
}
});
}
}
// Inclusion: 'in', 'out'
// Comparison: '>', '>='
for (const dependency of dependencies) {
uses++;
const targetPackageRawVersion = packageDependencies[dependency.name];
const hasTargetPackage = Boolean(targetPackageRawVersion);
const isInclusion = ['in', 'out'].includes(dependency.condition);
if (isInclusion) {
const [trigger, messageId] =
dependency.condition === 'in' ?
[hasTargetPackage, MESSAGE_ID_HAVE_PACKAGE] :
[!hasTargetPackage, MESSAGE_ID_DONT_HAVE_PACKAGE];
if (trigger) {
context.report({
loc: comment.loc,
messageId,
data: {
package: dependency.name,
message: parseTodoMessage(comment.value)
}
});
}
continue;
}
const todoVersion = tryToCoerceVersion(dependency.version);
const targetPackageVersion = tryToCoerceVersion(targetPackageRawVersion);
if (!hasTargetPackage || !targetPackageVersion) {
// Can't compare `¯\_(ツ)_/¯`
continue;
}
const compare = semverComparisonForOperator(dependency.condition);
if (compare(targetPackageVersion, todoVersion)) {
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_VERSION_MATCHES,
data: {
comparison: `${dependency.name} ${dependency.condition} ${dependency.version}`,
message: parseTodoMessage(comment.value)
}
});
}
}
const packageEngines = packageJson.engines || {};
for (const engine of engines) {
uses++;
const targetPackageRawEngineVersion = packageEngines.node;
const hasTargetEngine = Boolean(targetPackageRawEngineVersion);
if (!hasTargetEngine) {
continue;
}
const todoEngine = tryToCoerceVersion(engine.version);
const targetPackageEngineVersion = tryToCoerceVersion(
targetPackageRawEngineVersion
);
const compare = semverComparisonForOperator(engine.condition);
if (compare(targetPackageEngineVersion, todoEngine)) {
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_ENGINE_MATCHES,
data: {
comparison: `node${engine.condition}${engine.version}`,
message: parseTodoMessage(comment.value)
}
});
}
}
for (const unknown of unknowns) {
// In this case, check if there's just an '@' missing before a '>' or '>='.
const hasAt = unknown.includes('@');
const comparisonIndex = unknown.indexOf('>');
if (!hasAt && comparisonIndex !== -1) {
const testString = `${unknown.slice(
0,
comparisonIndex
)}@${unknown.slice(comparisonIndex)}`;
if (parseArgument(testString).type !== 'unknowns') {
uses++;
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_MISSING_AT_SYMBOL,
data: {
original: unknown,
fix: testString,
message: parseTodoMessage(comment.value)
}
});
continue;
}
}
const withoutWhitespaces = unknown.replace(/ /g, '');
if (parseArgument(withoutWhitespaces).type !== 'unknowns') {
uses++;
context.report({
loc: comment.loc,
messageId: MESSAGE_ID_REMOVE_WHITESPACES,
data: {
original: unknown,
fix: withoutWhitespaces,
message: parseTodoMessage(comment.value)
}
});
continue;
}
}
return uses === 0;
}
return {
Program() {
rules.Program(); // eslint-disable-line new-cap
}
};
};
const schema = [
{
type: 'object',
properties: {
terms: {
type: 'array',
items: {
type: 'string'
}
},
ignore: {
type: 'array',
uniqueItems: true
},
ignoreDatesOnPullRequests: {
type: 'boolean',
default: true
},
allowWarningComments: {
type: 'boolean',
default: false
}
},
additionalProperties: false
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
messages: {
[MESSAGE_ID_AVOID_MULTIPLE_DATES]:
'Avoid using multiple expiration dates in TODO: {{expirationDates}}. {{message}}',
[MESSAGE_ID_EXPIRED_TODO]:
'There is a TODO that is past due date: {{expirationDate}}. {{message}}',
[MESSAGE_ID_REACHED_PACKAGE_VERSION]:
'There is a TODO that is past due package version: {{comparison}}. {{message}}',
[MESSAGE_ID_AVOID_MULTIPLE_PACKAGE_VERSIONS]:
'Avoid using multiple package versions in TODO: {{versions}}. {{message}}',
[MESSAGE_ID_HAVE_PACKAGE]:
'There is a TODO that is deprecated since you installed: {{package}}. {{message}}',
[MESSAGE_ID_DONT_HAVE_PACKAGE]:
'There is a TODO that is deprecated since you uninstalled: {{package}}. {{message}}',
[MESSAGE_ID_VERSION_MATCHES]:
'There is a TODO match for package version: {{comparison}}. {{message}}',
[MESSAGE_ID_ENGINE_MATCHES]:
'There is a TODO match for Node.js version: {{comparison}}. {{message}}',
[MESSAGE_ID_REMOVE_WHITESPACES]:
'Avoid using whitespaces on TODO argument. On \'{{original}}\' use \'{{fix}}\'. {{message}}',
[MESSAGE_ID_MISSING_AT_SYMBOL]:
'Missing \'@\' on TODO argument. On \'{{original}}\' use \'{{fix}}\'. {{message}}',
...baseRule.meta.messages
},
schema
}
};
-173
View File
@@ -1,173 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const operatorTypes = {
gt: ['>'],
gte: ['>='],
ne: ['!==', '!=']
};
function reportError(context, node, message, fixDetails) {
context.report({
node,
message,
fix: fixDetails && (fixer => {
return fixer.replaceText(
node,
`${context.getSourceCode().getText(fixDetails.node)} ${fixDetails.operator} ${fixDetails.value}`
);
})
});
}
function checkZeroType(context, node) {
if (node.operator === '<' && node.right.value === 1) {
reportError(
context,
node,
'Zero `.length` should be compared with `=== 0`.',
{
node: node.left,
operator: '===',
value: 0
}
);
}
}
function checkNonZeroType(context, node, type) {
const {value} = node.right;
const {operator} = node;
switch (type) {
case 'greater-than':
if (
(operatorTypes.gte.includes(operator) && value === 1) ||
(operatorTypes.ne.includes(operator) && value === 0)
) {
reportError(
context,
node,
'Non-zero `.length` should be compared with `> 0`.',
{
node: node.left,
operator: '>',
value: 0
}
);
}
break;
case 'greater-than-or-equal':
if (
(operatorTypes.gt.includes(operator) && value === 0) ||
(operatorTypes.ne.includes(operator) && value === 0)
) {
reportError(
context,
node,
'Non-zero `.length` should be compared with `>= 1`.',
{
node: node.left,
operator: '>=',
value: 1
}
);
}
break;
case 'not-equal':
if (
(operatorTypes.gt.includes(operator) && value === 0) ||
(operatorTypes.gte.includes(operator) && value === 1)
) {
reportError(
context,
node,
'Non-zero `.length` should be compared with `!== 0`.',
{
node: node.left,
operator: '!==',
value: 0
}
);
}
break;
default:
break;
}
}
function checkBinaryExpression(context, node, options) {
if (
node.right.type === 'Literal' &&
node.left.type === 'MemberExpression' &&
node.left.property.type === 'Identifier' &&
node.left.property.name === 'length'
) {
checkZeroType(context, node);
checkNonZeroType(context, node, options['non-zero']);
}
}
function checkExpression(context, node) {
if (node.type === 'LogicalExpression') {
checkExpression(context, node.left);
checkExpression(context, node.right);
return;
}
if (node.type === 'UnaryExpression' && node.operator === '!') {
checkExpression(context, node.argument);
return;
}
if (node.type === 'BinaryExpression') {
checkBinaryExpression(context, node, context.options[0] || {});
return;
}
if (
node.type === 'MemberExpression' &&
node.property.type === 'Identifier' &&
node.property.name === 'length' &&
!node.computed
) {
reportError(context, node, '`length` property should be compared to a value.');
}
}
const create = context => {
return {
IfStatement: node => {
checkExpression(context, node.test);
},
ConditionalExpression: node => {
checkExpression(context, node.test);
}
};
};
const schema = [
{
type: 'object',
properties: {
'non-zero': {
enum: ['not-equal', 'greater-than', 'greater-than-or-equal']
}
}
}
];
module.exports = {
create,
meta: {
type: 'problem',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema
}
};
-253
View File
@@ -1,253 +0,0 @@
'use strict';
const path = require('path');
const {camelCase, kebabCase, snakeCase, upperFirst} = require('lodash');
const getDocumentationUrl = require('./utils/get-documentation-url');
const cartesianProductSamples = require('./utils/cartesian-product-samples');
const pascalCase = string => upperFirst(camelCase(string));
const numberRegex = /\d+/;
const PLACEHOLDER = '\uFFFF\uFFFF\uFFFF';
const PLACEHOLDER_REGEX = new RegExp(PLACEHOLDER, 'i');
const isIgnoredChar = char => !/^[a-z\d-_$]$/i.test(char);
function ignoreNumbers(fn) {
return string => {
const stack = [];
let execResult = numberRegex.exec(string);
while (execResult) {
stack.push(execResult[0]);
string = string.replace(execResult[0], PLACEHOLDER);
execResult = numberRegex.exec(string);
}
let withCase = fn(string);
while (stack.length > 0) {
withCase = withCase.replace(PLACEHOLDER_REGEX, stack.shift());
}
return withCase;
};
}
const cases = {
camelCase: {
fn: camelCase,
name: 'camel case'
},
kebabCase: {
fn: kebabCase,
name: 'kebab case'
},
snakeCase: {
fn: snakeCase,
name: 'snake case'
},
pascalCase: {
fn: pascalCase,
name: 'pascal case'
}
};
/**
Get the cases specified by the option.
@param {object} options
@returns {string[]} The chosen cases.
*/
function getChosenCases(options) {
if (options.case) {
return [options.case];
}
if (options.cases) {
const cases = Object.keys(options.cases)
.filter(cases => options.cases[cases]);
return cases.length > 0 ? cases : ['kebabCase'];
}
return ['kebabCase'];
}
function validateFilename(words, caseFunctions) {
return words
.filter(({ignored}) => !ignored)
.every(({word}) => caseFunctions.some(fn => fn(word) === word));
}
function fixFilename(words, caseFunctions, {leading, extension}) {
const replacements = words
.map(({word, ignored}) => ignored ? [word] : caseFunctions.map(fn => fn(word)));
const {
samples: combinations
} = cartesianProductSamples(replacements);
return combinations.map(parts => `${leading}${parts.join('')}${extension}`);
}
const leadingUnderscoresRegex = /^(?<leading>_+)(?<tailing>.*)$/;
function splitFilename(filename) {
const result = leadingUnderscoresRegex.exec(filename) || {groups: {}};
const {leading = '', tailing = filename} = result.groups;
const words = [];
let lastWord;
for (const char of tailing) {
const isIgnored = isIgnoredChar(char);
if (lastWord && lastWord.ignored === isIgnored) {
lastWord.word += char;
} else {
lastWord = {
word: char,
ignored: isIgnored
};
words.push(lastWord);
}
}
return {
leading,
words
};
}
/**
Turns `[a, b, c]` into `a, b, or c`.
@param {string[]} words
@returns {string}
*/
function englishishJoinWords(words) {
if (words.length === 1) {
return words[0];
}
if (words.length === 2) {
return `${words[0]} or ${words[1]}`;
}
words = words.slice();
const last = words.pop();
return `${words.join(', ')}, or ${last}`;
}
const create = context => {
const options = context.options[0] || {};
const chosenCases = getChosenCases(options);
const ignore = (options.ignore || []).map(item => {
if (item instanceof RegExp) {
return item;
}
return new RegExp(item, 'u');
});
const chosenCasesFunctions = chosenCases.map(case_ => ignoreNumbers(cases[case_].fn));
const filenameWithExtension = context.getFilename();
if (filenameWithExtension === '<input>' || filenameWithExtension === '<text>') {
return {};
}
return {
Program: node => {
const extension = path.extname(filenameWithExtension);
const filename = path.basename(filenameWithExtension, extension);
const base = filename + extension;
if (base === 'index.js' || ignore.some(regexp => regexp.test(base))) {
return;
}
const {leading, words} = splitFilename(filename);
const isValid = validateFilename(words, chosenCasesFunctions);
if (isValid) {
return;
}
const renamedFilenames = fixFilename(words, chosenCasesFunctions, {
leading,
extension
});
context.report({
node,
messageId: chosenCases.length > 1 ? 'renameToCases' : 'renameToCase',
data: {
chosenCases: englishishJoinWords(chosenCases.map(x => cases[x].name)),
renamedFilenames: englishishJoinWords(renamedFilenames.map(x => `\`${x}\``))
}
});
}
};
};
const schema = [
{
oneOf: [
{
properties: {
case: {
enum: [
'camelCase',
'snakeCase',
'kebabCase',
'pascalCase'
]
},
ignore: {
type: 'array',
uniqueItems: true
}
},
additionalProperties: false
},
{
properties: {
cases: {
properties: {
camelCase: {
type: 'boolean'
},
snakeCase: {
type: 'boolean'
},
kebabCase: {
type: 'boolean'
},
pascalCase: {
type: 'boolean'
}
},
additionalProperties: false
},
ignore: {
type: 'array',
uniqueItems: true
}
},
additionalProperties: false
}
]
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
schema,
messages: {
renameToCase: 'Filename is not in {{chosenCases}}. Rename it to {{renamedFilenames}}.',
renameToCases: 'Filename is not in {{chosenCases}}. Rename it to {{renamedFilenames}}.'
}
}
};
-55
View File
@@ -1,55 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const regexp = /^(?<package>@.*?\/.*?|[./]+?.*?)\/(?:\.|(?:index(?:\.js)?))?$/;
const isImportingIndex = value => regexp.test(value);
const normalize = value => value.replace(regexp, '$<package>');
const importIndex = (context, node, argument) => {
if (argument && isImportingIndex(argument.value)) {
context.report({
node,
message: 'Do not reference the index file directly.',
fix: fixer => fixer.replaceText(argument, `'${normalize(argument.value)}'`)
});
}
};
const create = context => {
const options = context.options[0] || {};
const rules = {
'CallExpression[callee.name="require"]': node => importIndex(context, node, node.arguments[0])
};
if (!options.ignoreImports) {
rules.ImportDeclaration = node => importIndex(context, node, node.source);
}
return rules;
};
const schema = [
{
type: 'object',
properties: {
ignoreImports: {
type: 'boolean',
default: false
}
},
additionalProperties: false
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
schema,
fixable: 'code'
}
};
-58
View File
@@ -1,58 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const builtins = require('./utils/builtins');
const isShadowed = require('./utils/is-shadowed');
const messages = {
enforce: 'Use `new {{name}}()` instead of `{{name}}()`.',
disallow: 'Use `{{name}}()` instead of `new {{name}}()`.'
};
const enforceNew = new Set(builtins.enforceNew);
const disallowNew = new Set(builtins.disallowNew);
const create = context => {
return {
CallExpression: node => {
const {callee} = node;
const {name} = callee;
if (enforceNew.has(name) && !isShadowed(context.getScope(), callee)) {
context.report({
node,
messageId: 'enforce',
data: {name},
fix: fixer => fixer.insertTextBefore(node, 'new ')
});
}
},
NewExpression: node => {
const {callee} = node;
const {name} = callee;
if (disallowNew.has(name) && !isShadowed(context.getScope(), callee)) {
context.report({
node,
messageId: 'disallow',
data: {name},
fix: fixer => fixer.removeRange([
node.range[0],
node.callee.range[0]
])
});
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages
}
};
-39
View File
@@ -1,39 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const disableRegex = /^eslint-disable(?:-next-line|-line)?(?<ruleId>$|(?:\s+(?:@(?:[\w-]+\/){1,2})?[\w-]+)?)/;
const create = context => ({
Program: node => {
for (const comment of node.comments) {
const value = comment.value.trim();
const result = disableRegex.exec(value);
if (
result && // It's a eslint-disable comment
!result.groups.ruleId // But it did not specify any rules
) {
context.report({
loc: {
start: {
...comment.loc.start,
column: -1
},
end: comment.loc.end
},
message: 'Specify the rules you want to disable.'
});
}
}
}
});
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
}
}
};
-36
View File
@@ -1,36 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const message = 'Use `Array.isArray()` instead of `instanceof Array`.';
const selector = [
'BinaryExpression',
'[operator="instanceof"]',
'[right.type="Identifier"]',
'[right.name="Array"]'
].join('');
const create = context => {
const sourceCode = context.getSourceCode();
return {
[selector]: node => context.report({
node,
message,
fix: fixer => fixer.replaceText(
node,
`Array.isArray(${sourceCode.getText(node.left)})`
)
})
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-93
View File
@@ -1,93 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const methodSelector = require('./utils/method-selector');
const replaceStringRaw = require('./utils/replace-string-raw');
const message = 'Do not use leading/trailing space between `console.{{method}}` parameters.';
const methods = [
'log',
'debug',
'info',
'warn',
'error'
];
const selector = methodSelector({
names: methods,
min: 1,
object: 'console'
});
// Find exactly one leading space, allow exactly one space
const fixLeadingSpace = value =>
value.length > 1 && value.charAt(0) === ' ' && value.charAt(1) !== ' ' ?
value.slice(1) :
value;
// Find exactly one trailing space, allow exactly one space
const fixTrailingSpace = value =>
value.length > 1 && value.charAt(value.length - 1) === ' ' && value.charAt(value.length - 2) !== ' ' ?
value.slice(0, -1) :
value;
const create = context => {
const sourceCode = context.getSourceCode();
const fixParamter = (node, index, parameters) => {
if (
!(node.type === 'Literal' && typeof node.value === 'string') &&
node.type !== 'TemplateLiteral'
) {
return;
}
const raw = sourceCode.getText(node).slice(1, -1);
let fixed = raw;
if (index !== 0) {
fixed = fixLeadingSpace(fixed);
}
if (index !== parameters.length - 1) {
fixed = fixTrailingSpace(fixed);
}
if (raw !== fixed) {
return {
node,
fixed
};
}
};
return {
[selector](node) {
const method = node.callee.property.name;
const fixedParameters = node.arguments
.map((parameter, index) => fixParamter(parameter, index, node.arguments))
.filter(Boolean);
for (const {node, fixed} of fixedParameters) {
context.report({
node,
message,
data: {method},
fix: fixer => replaceStringRaw(fixer, node, fixed)
});
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-177
View File
@@ -1,177 +0,0 @@
'use strict';
const {isParenthesized} = require('eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url');
const methodSelector = require('./utils/method-selector');
const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name';
const ERROR_WITHOUT_NAME_MESSAGE_ID = 'error-without-name';
const REPLACE_WITH_NAME_MESSAGE_ID = 'replace-with-name';
const REPLACE_WITHOUT_NAME_MESSAGE_ID = 'replace-without-name';
const iteratorMethods = [
['every'],
[
'filter', {
extraSelector: '[callee.object.name!="Vue"]'
}
],
['find'],
['findIndex'],
['flatMap'],
['forEach'],
['map'],
[
'reduce', {
parameters: [
'accumulator',
'element',
'index',
'array'
],
minParameters: 2,
ignore: []
}
],
[
'reduceRight', {
parameters: [
'accumulator',
'element',
'index',
'array'
],
minParameters: 2,
ignore: []
}
],
['some']
].map(([method, options]) => {
options = {
parameters: ['element', 'index', 'array'],
ignore: ['Boolean'],
minParameters: 1,
extraSelector: '',
...options
};
return [method, options];
});
const ignoredCallee = [
'Promise',
'React.children',
'lodash',
'underscore',
'_',
'Async',
'async'
];
const toSelector = name => {
const splitted = name.split('.');
return `[callee.${'object.'.repeat(splitted.length)}name!="${splitted.shift()}"]`;
};
// Select all the call expressions except the ones present in the blacklist
const ignoredCalleeSelector = [
// `this.{map, filter, …}()`
'[callee.object.type!="ThisExpression"]',
...ignoredCallee.map(name => toSelector(name))
].join('');
function check(context, node, method, options) {
const {type} = node;
const name = type === 'Identifier' ? node.name : '';
if (type === 'Identifier' && options.ignore.includes(name)) {
return;
}
const problem = {
node,
messageId: name ? ERROR_WITH_NAME_MESSAGE_ID : ERROR_WITHOUT_NAME_MESSAGE_ID,
data: {
name,
method
},
suggest: []
};
const {parameters, minParameters} = options;
for (let parameterLength = minParameters; parameterLength <= parameters.length; parameterLength++) {
const suggestionParameters = parameters.slice(0, parameterLength).join(', ');
const suggest = {
messageId: name ? REPLACE_WITH_NAME_MESSAGE_ID : REPLACE_WITHOUT_NAME_MESSAGE_ID,
data: {
name,
parameters: suggestionParameters
},
fix: fixer => {
const sourceCode = context.getSourceCode();
let nodeText = sourceCode.getText(node);
if (isParenthesized(node, sourceCode) || type === 'ConditionalExpression') {
nodeText = `(${nodeText})`;
}
return fixer.replaceText(
node,
`(${suggestionParameters}) => ${nodeText}(${suggestionParameters})`
);
}
};
problem.suggest.push(suggest);
}
context.report(problem);
}
const ignoredFirstArgumentSelector = `:not(${
[
'[arguments.0.type="FunctionExpression"]',
'[arguments.0.type="ArrowFunctionExpression"]',
'[arguments.0.type="Literal"]',
'[arguments.0.type="Identifier"][arguments.0.name="undefined"]'
].join(',')
})`;
const create = context => {
const sourceCode = context.getSourceCode();
const rules = {};
for (const [method, options] of iteratorMethods) {
const selector = [
methodSelector({
name: method,
min: 1,
max: 2
}),
options.extraSelector,
ignoredCalleeSelector,
ignoredFirstArgumentSelector
].join('');
rules[selector] = node => {
const [iterator] = node.arguments;
check(context, iterator, method, options, sourceCode);
};
}
return rules;
};
module.exports = {
create,
meta: {
type: 'problem',
docs: {
url: getDocumentationUrl(__filename)
},
messages: {
[ERROR_WITH_NAME_MESSAGE_ID]: 'Do not pass function `{{name}}` directly to `.{{method}}(…)`.',
[ERROR_WITHOUT_NAME_MESSAGE_ID]: 'Do not pass function directly to `.{{method}}(…)`.',
[REPLACE_WITH_NAME_MESSAGE_ID]: 'Replace function `{{name}}` with `… => {{name}}({{parameters}})`.',
[REPLACE_WITHOUT_NAME_MESSAGE_ID]: 'Replace function with `… => …({{parameters}})`.'
}
}
};
-398
View File
@@ -1,398 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const isLiteralValue = require('./utils/is-literal-value');
const defaultElementName = 'element';
const isLiteralZero = node => isLiteralValue(node, 0);
const isLiteralOne = node => isLiteralValue(node, 1);
const isIdentifierWithName = (node, name) => node && node.type === 'Identifier' && node.name === name;
const getIndexIdentifierName = forStatement => {
const {init: variableDeclaration} = forStatement;
if (
!variableDeclaration ||
variableDeclaration.type !== 'VariableDeclaration'
) {
return;
}
if (variableDeclaration.declarations.length !== 1) {
return;
}
const [variableDeclarator] = variableDeclaration.declarations;
if (!isLiteralZero(variableDeclarator.init)) {
return;
}
if (variableDeclarator.id.type !== 'Identifier') {
return;
}
return variableDeclarator.id.name;
};
const getStrictComparisonOperands = binaryExpression => {
if (binaryExpression.operator === '<') {
return {
lesser: binaryExpression.left,
greater: binaryExpression.right
};
}
if (binaryExpression.operator === '>') {
return {
lesser: binaryExpression.right,
greater: binaryExpression.left
};
}
};
const getArrayIdentifierNameFromBinaryExpression = (binaryExpression, indexIdentifierName) => {
const operands = getStrictComparisonOperands(binaryExpression);
if (!operands) {
return;
}
const {lesser, greater} = operands;
if (!isIdentifierWithName(lesser, indexIdentifierName)) {
return;
}
if (greater.type !== 'MemberExpression') {
return;
}
if (
greater.object.type !== 'Identifier' ||
greater.property.type !== 'Identifier'
) {
return;
}
if (greater.property.name !== 'length') {
return;
}
return greater.object.name;
};
const getArrayIdentifierName = (forStatement, indexIdentifierName) => {
const {test} = forStatement;
if (!test || test.type !== 'BinaryExpression') {
return;
}
return getArrayIdentifierNameFromBinaryExpression(test, indexIdentifierName);
};
const isLiteralOnePlusIdentifierWithName = (node, identifierName) => {
if (node && node.type === 'BinaryExpression' && node.operator === '+') {
return (isIdentifierWithName(node.left, identifierName) && isLiteralOne(node.right)) ||
(isIdentifierWithName(node.right, identifierName) && isLiteralOne(node.left));
}
return false;
};
const checkUpdateExpression = (forStatement, indexIdentifierName) => {
const {update} = forStatement;
if (!update) {
return false;
}
if (update.type === 'UpdateExpression') {
return update.operator === '++' && isIdentifierWithName(update.argument, indexIdentifierName);
}
if (
update.type === 'AssignmentExpression' &&
isIdentifierWithName(update.left, indexIdentifierName)
) {
if (update.operator === '+=') {
return isLiteralOne(update.right);
}
if (update.operator === '=') {
return isLiteralOnePlusIdentifierWithName(update.right, indexIdentifierName);
}
}
return false;
};
const isOnlyArrayOfIndexVariableRead = (arrayReferences, indexIdentifierName) => {
return arrayReferences.every(reference => {
const node = reference.identifier.parent;
if (node.type !== 'MemberExpression') {
return false;
}
if (node.property.name !== indexIdentifierName) {
return false;
}
if (
node.parent.type === 'AssignmentExpression' &&
node.parent.left === node
) {
return false;
}
return true;
});
};
const getRemovalRange = (node, sourceCode) => {
const declarationNode = node.parent;
if (declarationNode.declarations.length === 1) {
const {line} = sourceCode.getLocFromIndex(declarationNode.range[0]);
const lineText = sourceCode.lines[line - 1];
const isOnlyNodeOnLine = lineText.trim() === sourceCode.getText(declarationNode);
return isOnlyNodeOnLine ? [
sourceCode.getIndexFromLoc({line, column: 0}),
sourceCode.getIndexFromLoc({line: line + 1, column: 0})
] : declarationNode.range;
}
const index = declarationNode.declarations.indexOf(node);
if (index === 0) {
return [
node.range[0],
declarationNode.declarations[1].range[0]
];
}
return [
declarationNode.declarations[index - 1].range[1],
node.range[1]
];
};
const resolveIdentifierName = (name, scope) => {
while (scope) {
const variable = scope.set.get(name);
if (variable) {
return variable;
}
scope = scope.upper;
}
return undefined;
};
const scopeContains = (ancestor, descendant) => {
while (descendant) {
if (descendant === ancestor) {
return true;
}
descendant = descendant.upper;
}
return false;
};
const nodeContains = (ancestor, descendant) => {
while (descendant) {
if (descendant === ancestor) {
return true;
}
descendant = descendant.parent;
}
return false;
};
const isIndexVariableUsedElsewhereInTheLoopBody = (indexVariable, bodyScope, arrayIdentifierName) => {
const inBodyReferences = indexVariable.references.filter(reference => scopeContains(bodyScope, reference.from));
const referencesOtherThanArrayAccess = inBodyReferences.filter(reference => {
const node = reference.identifier.parent;
if (node.type !== 'MemberExpression') {
return true;
}
if (node.object.name !== arrayIdentifierName) {
return true;
}
return false;
});
return referencesOtherThanArrayAccess.length > 0;
};
const isIndexVariableAssignedToInTheLoopBody = (indexVariable, bodyScope) => {
return indexVariable.references
.filter(reference => scopeContains(bodyScope, reference.from))
.some(inBodyReference => inBodyReference.isWrite());
};
const someVariablesLeakOutOfTheLoop = (forStatement, variables, forScope) => {
return variables.some(variable => {
return !variable.references.every(reference => {
return scopeContains(forScope, reference.from) ||
nodeContains(forStatement, reference.identifier);
});
});
};
const getReferencesInChildScopes = (scope, name) => {
const references = scope.references.filter(reference => reference.identifier.name === name);
return [
...references,
...scope.childScopes
.map(s => getReferencesInChildScopes(s, name))
.reduce((accumulator, scopeReferences) => [...accumulator, ...scopeReferences], [])
];
};
const create = context => {
const sourceCode = context.getSourceCode();
const {scopeManager} = sourceCode;
return {
ForStatement(node) {
const indexIdentifierName = getIndexIdentifierName(node);
if (!indexIdentifierName) {
return;
}
const arrayIdentifierName = getArrayIdentifierName(node, indexIdentifierName);
if (!arrayIdentifierName) {
return;
}
if (!checkUpdateExpression(node, indexIdentifierName)) {
return;
}
if (!node.body || node.body.type !== 'BlockStatement') {
return;
}
const forScope = scopeManager.acquire(node);
const bodyScope = scopeManager.acquire(node.body);
if (!bodyScope) {
return;
}
const indexVariable = resolveIdentifierName(indexIdentifierName, bodyScope);
if (isIndexVariableAssignedToInTheLoopBody(indexVariable, bodyScope)) {
return;
}
const arrayReferences = getReferencesInChildScopes(bodyScope, arrayIdentifierName);
if (arrayReferences.length === 0) {
return;
}
if (!isOnlyArrayOfIndexVariableRead(arrayReferences, indexIdentifierName)) {
return;
}
const problem = {
node,
message: 'Use a `for-of` loop instead of this `for` loop.'
};
const elementReference = arrayReferences.find(reference => {
const node = reference.identifier.parent;
if (node.parent.type !== 'VariableDeclarator') {
return false;
}
return true;
});
const elementNode = elementReference && elementReference.identifier.parent.parent;
const elementIdentifierName = elementNode && elementNode.id.name;
const elementVariable = elementIdentifierName && resolveIdentifierName(elementIdentifierName, bodyScope);
const shouldFix = !someVariablesLeakOutOfTheLoop(node, [indexVariable, elementVariable].filter(Boolean), forScope);
if (shouldFix) {
problem.fix = fixer => {
const shouldGenerateIndex = isIndexVariableUsedElsewhereInTheLoopBody(indexVariable, bodyScope, arrayIdentifierName);
const index = indexIdentifierName;
const element = elementIdentifierName || defaultElementName;
const array = arrayIdentifierName;
let declarationElement = element;
let declarationType = 'const';
let removeDeclaration = true;
if (
elementNode &&
(elementNode.id.type === 'ObjectPattern' || elementNode.id.type === 'ArrayPattern')
) {
removeDeclaration = arrayReferences.length === 1;
if (removeDeclaration) {
declarationType = elementNode.parent.kind;
declarationElement = sourceCode.getText(elementNode.id);
}
}
const replacement = shouldGenerateIndex ?
`${declarationType} [${index}, ${declarationElement}] of ${array}.entries()` :
`${declarationType} ${declarationElement} of ${array}`;
return [
fixer.replaceTextRange([
node.init.range[0],
node.update.range[1]
], replacement),
...arrayReferences.map(reference => {
if (reference === elementReference) {
return undefined;
}
return fixer.replaceText(reference.identifier.parent, element);
}),
elementNode && (
removeDeclaration ?
fixer.removeRange(getRemovalRange(elementNode, sourceCode)) :
fixer.replaceText(elementNode.init, element)
)
].filter(Boolean);
};
}
context.report(problem);
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-42
View File
@@ -1,42 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const replaceTemplateElement = require('./utils/replace-template-element');
function checkEscape(context, node, value) {
const fixedValue = value.replace(/(?<=(?:^|[^\\])(?:\\\\)*\\)x/g, 'u00');
if (value !== fixedValue) {
context.report({
node,
message: 'Use Unicode escapes instead of hexadecimal escapes.',
fix: fixer =>
node.type === 'TemplateElement' ?
replaceTemplateElement(fixer, node, fixedValue) :
fixer.replaceText(node, fixedValue)
});
}
}
const create = context => {
return {
Literal: node => {
if (node.regex || typeof node.value === 'string') {
checkEscape(context, node, node.raw);
}
},
TemplateElement: node => {
checkEscape(context, node, node.value.raw);
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-202
View File
@@ -1,202 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const MESSAGE_ID = 'noKeywordPrefix';
const prepareOptions = ({
blacklist,
checkProperties = true,
onlyCamelCase = true
} = {}) => {
return {
blacklist: (blacklist || [
'new',
'class'
]),
checkProperties,
onlyCamelCase
};
};
function findKeywordPrefix(name, options) {
return options.blacklist.find(keyword => {
const suffix = options.onlyCamelCase ? '[A-Z]' : '.';
const regex = new RegExp(`^${keyword}${suffix}`);
return name.match(regex);
});
}
function checkMemberExpression(report, node, options) {
const {name} = node;
const keyword = findKeywordPrefix(name, options);
const effectiveParent = (node.parent.type === 'MemberExpression') ? node.parent.parent : node.parent;
if (!options.checkProperties) {
return;
}
if (node.parent.object.type === 'Identifier' && node.parent.object.name === node.name && Boolean(keyword)) {
report(node, keyword);
} else if (
effectiveParent.type === 'AssignmentExpression' &&
Boolean(keyword) &&
(effectiveParent.right.type !== 'MemberExpression' || effectiveParent.left.type === 'MemberExpression') &&
effectiveParent.left.property.name === node.name
) {
report(node, keyword);
}
}
function checkObjectPattern(report, node, options) {
const {name} = node;
const keyword = findKeywordPrefix(name, options);
if (node.parent.shorthand && node.parent.value.left && Boolean(keyword)) {
report(node, keyword);
}
const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
if (Boolean(keyword) && node.parent.computed) {
report(node, keyword);
}
// Prevent checking righthand side of destructured object
if (node.parent.key === node && node.parent.value !== node) {
return true;
}
const valueIsInvalid = node.parent.value.name && Boolean(keyword);
// Ignore destructuring if the option is set, unless a new identifier is created
if (valueIsInvalid && !assignmentKeyEqualsValue) {
report(node, keyword);
}
return false;
}
// Core logic copied from:
// https://github.com/eslint/eslint/blob/master/lib/rules/camelcase.js
const create = context => {
const options = prepareOptions(context.options[0]);
// Contains reported nodes to avoid reporting twice on destructuring with shorthand notation
const reported = [];
const ALLOWED_PARENT_TYPES = new Set(['CallExpression', 'NewExpression']);
function report(node, keyword) {
if (!reported.includes(node)) {
reported.push(node);
context.report({
node,
messageId: MESSAGE_ID,
data: {
name: node.name,
keyword
}
});
}
}
return {
Identifier: node => {
const {name} = node;
const keyword = findKeywordPrefix(name, options);
const effectiveParent = (node.parent.type === 'MemberExpression') ? node.parent.parent : node.parent;
if (node.parent.type === 'MemberExpression') {
checkMemberExpression(report, node, options);
} else if (
node.parent.type === 'Property' ||
node.parent.type === 'AssignmentPattern'
) {
if (node.parent.parent && node.parent.parent.type === 'ObjectPattern') {
const finished = checkObjectPattern(report, node, options);
if (finished) {
return;
}
}
if (
!options.checkProperties
) {
return;
}
// Don't check right hand side of AssignmentExpression to prevent duplicate warnings
if (
Boolean(keyword) &&
!ALLOWED_PARENT_TYPES.has(effectiveParent.type) &&
!(node.parent.right === node)
) {
report(node, keyword);
}
// Check if it's an import specifier
} else if (
[
'ImportSpecifier',
'ImportNamespaceSpecifier',
'ImportDefaultSpecifier'
].includes(node.parent.type)
) {
// Report only if the local imported identifier is invalid
if (
Boolean(keyword) &&
node.parent.local &&
node.parent.local.name === node.name
) {
report(node, keyword);
}
// Report anything that is invalid that isn't a CallExpression
} else if (
Boolean(keyword) &&
!ALLOWED_PARENT_TYPES.has(effectiveParent.type)
) {
report(node, keyword);
}
}
};
};
const schema = [
{
type: 'object',
properties: {
blacklist: {
type: 'array',
items: [
{
type: 'string'
}
],
minItems: 0,
uniqueItems: true
},
checkProperties: {
type: 'boolean'
},
onlyCamelCase: {
type: 'boolean'
}
},
additionalProperties: false
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
schema,
messages: {
[MESSAGE_ID]: 'Do not prefix identifiers with keyword `{{keyword}}`.'
}
}
};
-50
View File
@@ -1,50 +0,0 @@
'use strict';
const {isParenthesized} = require('eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url');
const create = context => {
const sourceCode = context.getSourceCode();
return {
ConditionalExpression: node => {
const nodesToCheck = [node.alternate, node.consequent];
for (const childNode of nodesToCheck) {
if (childNode.type !== 'ConditionalExpression') {
continue;
}
const message = 'Do not nest ternary expressions.';
// Nesting more than one level not allowed.
if (
childNode.alternate.type === 'ConditionalExpression' ||
childNode.consequent.type === 'ConditionalExpression'
) {
context.report({node, message});
break;
} else if (!isParenthesized(childNode, sourceCode)) {
context.report({
node: childNode,
message,
fix: fixer => [
fixer.insertTextBefore(childNode, '('),
fixer.insertTextAfter(childNode, ')')
]
});
}
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-45
View File
@@ -1,45 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const inferMethod = arguments_ => {
if (arguments_.length > 0) {
const [firstArgument] = arguments_;
if (
firstArgument.type === 'Literal' &&
typeof firstArgument.value === 'number'
) {
return 'alloc';
}
}
return 'from';
};
const create = context => {
return {
'NewExpression[callee.name="Buffer"]': node => {
const method = inferMethod(node.arguments);
const range = [
node.range[0],
node.callee.range[1]
];
context.report({
node,
message: `\`new Buffer()\` is deprecated, use \`Buffer.${method}()\` instead.`,
fix: fixer => fixer.replaceTextRange(range, `Buffer.${method}`)
});
}
};
};
module.exports = {
create,
meta: {
type: 'problem',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-108
View File
@@ -1,108 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const methodSelector = require('./utils/method-selector');
const ERROR_MESSAGE_ID = 'error';
const SUGGESTION_REPLACE_MESSAGE_ID = 'replace';
const SUGGESTION_REMOVE_MESSAGE_ID = 'remove';
const objectCreateSelector = methodSelector({
object: 'Object',
name: 'create',
length: 1
});
const selector = [
`:not(${objectCreateSelector})`,
'>',
'Literal',
'[raw="null"]'
].join('');
const isLooseEqual = node => node.type === 'BinaryExpression' && ['==', '!='].includes(node.operator);
const isStrictEqual = node => node.type === 'BinaryExpression' && ['===', '!=='].includes(node.operator);
const create = context => {
const {checkStrictEquality} = {
checkStrictEquality: false,
...context.options[0]
};
return {
[selector]: node => {
const problem = {
node,
messageId: ERROR_MESSAGE_ID
};
/* istanbul ignore next */
const {parent = {}} = node;
if (!checkStrictEquality && isStrictEqual(parent)) {
return;
}
const fix = fixer => fixer.replaceText(node, 'undefined');
const replaceSuggestion = {
messageId: SUGGESTION_REPLACE_MESSAGE_ID,
fix
};
if (isLooseEqual(parent)) {
problem.fix = fix;
} else if (parent.type === 'ReturnStatement' && parent.argument === node) {
problem.suggest = [
{
messageId: SUGGESTION_REMOVE_MESSAGE_ID,
fix: fixer => fixer.remove(node)
},
replaceSuggestion
];
} else if (parent.type === 'VariableDeclarator' && parent.init === node && parent.parent.kind !== 'const') {
problem.suggest = [
{
messageId: SUGGESTION_REMOVE_MESSAGE_ID,
fix: fixer => fixer.removeRange([parent.id.range[1], node.range[1]])
},
replaceSuggestion
];
} else {
problem.suggest = [
replaceSuggestion
];
}
context.report(problem);
}
};
};
const schema = [
{
type: 'object',
properties: {
checkStrictEquality: {
type: 'boolean',
default: false
}
},
additionalProperties: false
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
messages: {
[ERROR_MESSAGE_ID]: 'Use `undefined` instead of `null`.',
[SUGGESTION_REPLACE_MESSAGE_ID]: 'Replace `null` with `undefined`.',
[SUGGESTION_REMOVE_MESSAGE_ID]: 'Remove `null`.'
},
schema,
fixable: 'code'
}
};
-87
View File
@@ -1,87 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const methodSelector = require('./utils/method-selector');
const message = 'Only use `process.exit()` in CLI apps. Throw an error instead.';
const importWorkerThreadsSelector = [
// `require('worker_threads')`
[
'CallExpression',
'[callee.type="Identifier"]',
'[callee.name="require"]',
'[arguments.length=1]',
'[arguments.0.type="Literal"]',
'[arguments.0.value="worker_threads"]'
].join(''),
// `import workerThreads from 'worker_threads'`
[
'ImportDeclaration',
'[source.type="Literal"]',
'[source.value="worker_threads"]'
].join('')
].join(', ');
const processOnOrOnceCallSelector = methodSelector({
object: 'process',
names: ['on', 'once'],
min: 1
});
const processExitCallSelector = methodSelector({
object: 'process',
name: 'exit'
});
const create = context => {
const startsWithHashBang = context.getSourceCode().lines[0].indexOf('#!') === 0;
if (startsWithHashBang) {
return {};
}
let processEventHandler;
// Only report if it's outside an worker thread context. See #328.
let requiredWorkerThreadsModule = false;
const problemNodes = [];
return {
// Check `worker_threads` require / import
[importWorkerThreadsSelector]: () => {
requiredWorkerThreadsModule = true;
},
// Check `process.on` / `process.once` call
[processOnOrOnceCallSelector]: node => {
processEventHandler = node;
},
// Check `process.exit` call
[processExitCallSelector]: node => {
if (!processEventHandler) {
problemNodes.push(node);
}
},
'CallExpression:exit': node => {
if (node === processEventHandler) {
processEventHandler = undefined;
}
},
'Program:exit': () => {
if (!requiredWorkerThreadsModule) {
for (const node of problemNodes) {
context.report({
node,
message
});
}
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
}
}
};
@@ -1,29 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const message = 'Array destructuring may not contain consecutive ignored values.';
const isCommaFollowedWithComma = (element, index, array) =>
element === null && array[index + 1] === null;
const create = context => {
return {
'ArrayPattern[elements.length>=3]': node => {
if (node.elements.some((element, index, array) => isCommaFollowedWithComma(element, index, array))) {
context.report({
node,
message
});
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
}
}
};
-62
View File
@@ -1,62 +0,0 @@
'use strict';
const safeRegex = require('safe-regex');
const getDocumentationUrl = require('./utils/get-documentation-url');
const message = 'Unsafe regular expression.';
const create = context => {
return {
'Literal[regex]': node => {
// Handle regex literal inside RegExp constructor in the other handler
if (
node.parent.type === 'NewExpression' &&
node.parent.callee.name === 'RegExp'
) {
return;
}
if (!safeRegex(node.value)) {
context.report({
node,
message
});
}
},
'NewExpression[callee.name="RegExp"]': node => {
const arguments_ = node.arguments;
if (arguments_.length === 0 || arguments_[0].type !== 'Literal') {
return;
}
const hasRegExp = arguments_[0].regex;
let pattern;
let flags;
if (hasRegExp) {
({pattern} = arguments_[0].regex);
flags = arguments_[1] && arguments_[1].type === 'Literal' ? arguments_[1].value : arguments_[0].regex.flags;
} else {
pattern = arguments_[0].value;
flags = arguments_[1] && arguments_[1].type === 'Literal' ? arguments_[1].value : '';
}
if (!safeRegex(`/${pattern}/${flags}`)) {
context.report({
node,
message
});
}
}
};
};
module.exports = {
create,
meta: {
type: 'problem',
docs: {
url: getDocumentationUrl(__filename)
}
}
};
-245
View File
@@ -1,245 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const getDeclaratorOrPropertyValue = declaratorOrProperty => {
return declaratorOrProperty.init || declaratorOrProperty.value;
};
const isMemberExpressionCall = memberExpression => {
return (
memberExpression.parent &&
memberExpression.parent.type === 'CallExpression' &&
memberExpression.parent.callee === memberExpression
);
};
const isMemberExpressionAssignment = memberExpression => {
return (
memberExpression.parent &&
memberExpression.parent.type === 'AssignmentExpression'
);
};
const isMemberExpressionComputedBeyondPrediction = memberExpression => {
return (
memberExpression.computed && memberExpression.property.type !== 'Literal'
);
};
const specialProtoPropertyKey = {
type: 'Identifier',
name: '__proto__'
};
const propertyKeysEqual = (keyA, keyB) => {
if (keyA.type === 'Identifier') {
if (keyB.type === 'Identifier') {
return keyA.name === keyB.name;
}
if (keyB.type === 'Literal') {
return keyA.name === keyB.value;
}
}
if (keyA.type === 'Literal') {
if (keyB.type === 'Identifier') {
return keyA.value === keyB.name;
}
if (keyB.type === 'Literal') {
return keyA.value === keyB.value;
}
}
return false;
};
const objectPatternMatchesObjectExprPropertyKey = (pattern, key) => {
return pattern.properties.some(property => {
if (property.type === 'ExperimentalRestProperty') {
return true;
}
return propertyKeysEqual(property.key, key);
});
};
const isLeafDeclaratorOrProperty = declaratorOrProperty => {
const value = getDeclaratorOrPropertyValue(declaratorOrProperty);
if (!value) {
return true;
}
if (value.type !== 'ObjectExpression') {
return true;
}
return false;
};
const isUnusedVariable = variable => {
const hasReadReference = variable.references.some(reference => reference.isRead());
return !hasReadReference;
};
const create = context => {
const getPropertyDisplayName = property => {
if (property.key.type === 'Identifier') {
return property.key.name;
}
if (property.key.type === 'Literal') {
return property.key.value;
}
return context.getSource(property.key);
};
const checkProperty = (property, references, path) => {
if (references.length === 0) {
context.report({
node: property,
message: 'Property `{{name}}` is defined but never used.',
data: {
name: getPropertyDisplayName(property)
}
});
return;
}
checkObject(property, references, path);
};
const checkProperties = (objectExpression, references, path = []) => {
objectExpression.properties.forEach(property => {
const {key} = property;
if (!key) {
return;
}
if (propertyKeysEqual(key, specialProtoPropertyKey)) {
return;
}
const nextPath = path.concat(key);
const nextReferences = references
.map(reference => {
const {parent} = reference.identifier;
if (reference.init) {
if (
parent.type === 'VariableDeclarator' &&
parent.parent.type === 'VariableDeclaration' &&
parent.parent.parent.type === 'ExportNamedDeclaration'
) {
return {identifier: parent};
}
return undefined;
}
if (parent.type === 'MemberExpression') {
if (
isMemberExpressionAssignment(parent) ||
isMemberExpressionCall(parent) ||
isMemberExpressionComputedBeyondPrediction(parent) ||
propertyKeysEqual(parent.property, key)
) {
return {identifier: parent};
}
return undefined;
}
if (
parent.type === 'VariableDeclarator' &&
parent.id.type === 'ObjectPattern'
) {
if (objectPatternMatchesObjectExprPropertyKey(parent.id, key)) {
return {identifier: parent};
}
return undefined;
}
if (
parent.type === 'AssignmentExpression' &&
parent.left.type === 'ObjectPattern'
) {
if (objectPatternMatchesObjectExprPropertyKey(parent.left, key)) {
return {identifier: parent};
}
return undefined;
}
return reference;
})
.filter(Boolean);
checkProperty(property, nextReferences, nextPath);
});
};
const checkObject = (declaratorOrProperty, references, path) => {
if (isLeafDeclaratorOrProperty(declaratorOrProperty)) {
return;
}
const value = getDeclaratorOrPropertyValue(declaratorOrProperty);
checkProperties(value, references, path);
};
const checkVariable = variable => {
if (variable.defs.length !== 1) {
return;
}
if (isUnusedVariable(variable)) {
return;
}
const [definition] = variable.defs;
checkObject(definition.node, variable.references);
};
const checkVariables = scope => {
scope.variables.forEach(variable => checkVariable(variable));
};
const checkChildScopes = scope => {
scope.childScopes.forEach(scope => checkScope(scope));
};
const checkScope = scope => {
if (scope.type === 'global') {
return checkChildScopes(scope);
}
checkVariables(scope);
return checkChildScopes(scope);
};
return {
'Program:exit'() {
checkScope(context.getScope());
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
}
}
};
-61
View File
@@ -1,61 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const MESSAGE_ZERO_FRACTION = 'Don\'t use a zero fraction in the number.';
const MESSAGE_DANGLING_DOT = 'Don\'t use a dangling dot in the number.';
// Groups:
// 1. Integer part.
// 2. Dangling dot or dot with zeroes.
// 3. Dot with digits except last zeroes.
// 4. Scientific notation.
const RE_DANGLINGDOT_OR_ZERO_FRACTIONS = /^(?<integerPart>[+-]?\d*)(?:(?<dotAndZeroes>\.0*)|(?<dotAndDigits>\.\d*[1-9])0+)(?<scientificNotationSuffix>e[+-]?\d+)?$/;
const create = context => {
return {
Literal: node => {
if (typeof node.value !== 'number') {
return;
}
const match = RE_DANGLINGDOT_OR_ZERO_FRACTIONS.exec(node.raw);
if (match === null) {
return;
}
const {
integerPart,
dotAndZeroes,
dotAndDigits,
scientificNotationSuffix
} = match.groups;
const isDanglingDot = dotAndZeroes === '.';
context.report({
node,
message: isDanglingDot ? MESSAGE_DANGLING_DOT : MESSAGE_ZERO_FRACTION,
fix: fixer => {
let wantedString = dotAndZeroes === undefined ? integerPart + dotAndDigits : integerPart;
if (scientificNotationSuffix !== undefined) {
wantedString += scientificNotationSuffix;
}
return fixer.replaceText(node, wantedString);
}
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-45
View File
@@ -1,45 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const fix = (value, isBigInt) => {
value = value.toLowerCase();
if (value.startsWith('0x')) {
value = '0x' + value.slice(2).toUpperCase();
}
return `${value}${isBigInt ? 'n' : ''}`;
};
const create = context => {
return {
Literal: node => {
const {value, raw, bigint} = node;
const isBigInt = Boolean(bigint);
if (typeof value !== 'number' && !isBigInt) {
return;
}
const fixed = fix(isBigInt ? bigint : raw, isBigInt);
if (raw !== fixed) {
context.report({
node,
message: 'Invalid number literal casing.',
fix: fixer => fixer.replaceText(node, fixed)
});
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-170
View File
@@ -1,170 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const domEventsJson = require('./utils/dom-events.json');
const message = 'Prefer `{{replacement}}` over `{{method}}`.{{extra}}';
const extraMessages = {
beforeunload: 'Use `event.preventDefault(); event.returnValue = \'foo\'` to trigger the prompt.',
message: 'Note that there is difference between `SharedWorker#onmessage` and `SharedWorker#addEventListener(\'message\')`.'
};
const nestedEvents = Object.values(domEventsJson);
const eventTypes = new Set(nestedEvents.reduce((accumulatorEvents, events) => accumulatorEvents.concat(events), []));
const getEventMethodName = memberExpression => memberExpression.property.name;
const getEventTypeName = eventMethodName => eventMethodName.slice('on'.length);
const fixCode = (fixer, sourceCode, assignmentNode, memberExpression) => {
const eventTypeName = getEventTypeName(getEventMethodName(memberExpression));
const eventObjectCode = sourceCode.getText(memberExpression.object);
const fncCode = sourceCode.getText(assignmentNode.right);
const fixedCodeStatement = `${eventObjectCode}.addEventListener('${eventTypeName}', ${fncCode})`;
return fixer.replaceText(assignmentNode, fixedCodeStatement);
};
const shouldFixBeforeUnload = (assignedExpression, nodeReturnsSomething) => {
if (
assignedExpression.type !== 'ArrowFunctionExpression' &&
assignedExpression.type !== 'FunctionExpression'
) {
return false;
}
if (assignedExpression.body.type !== 'BlockStatement') {
return false;
}
return !nodeReturnsSomething.get(assignedExpression);
};
const isClearing = node => {
if (node.type === 'Literal') {
return node.raw === 'null';
}
if (node.type === 'Identifier') {
return node.name === 'undefined';
}
return false;
};
const create = context => {
const options = context.options[0] || {};
const excludedPackages = new Set(options.excludedPackages || ['koa', 'sax']);
let isDisabled;
const nodeReturnsSomething = new WeakMap();
let codePathInfo;
return {
onCodePathStart(codePath, node) {
codePathInfo = {
node,
upper: codePathInfo,
returnsSomething: false
};
},
onCodePathEnd() {
nodeReturnsSomething.set(codePathInfo.node, codePathInfo.returnsSomething);
codePathInfo = codePathInfo.upper;
},
'CallExpression[callee.name="require"] > Literal'(node) {
if (!isDisabled && excludedPackages.has(node.value)) {
isDisabled = true;
}
},
'ImportDeclaration > Literal'(node) {
if (!isDisabled && excludedPackages.has(node.value)) {
isDisabled = true;
}
},
ReturnStatement(node) {
codePathInfo.returnsSomething = codePathInfo.returnsSomething || Boolean(node.argument);
},
'AssignmentExpression:exit'(node) {
if (isDisabled) {
return;
}
const {left: memberExpression, right: assignedExpression} = node;
if (memberExpression.type !== 'MemberExpression') {
return;
}
const eventMethodName = getEventMethodName(memberExpression);
if (!eventMethodName || !eventMethodName.startsWith('on')) {
return;
}
const eventTypeName = getEventTypeName(eventMethodName);
if (!eventTypes.has(eventTypeName)) {
return;
}
let replacement = 'addEventListener';
let extra = '';
let fix;
if (isClearing(assignedExpression)) {
replacement = 'removeEventListener';
} else if (
eventTypeName === 'beforeunload' &&
!shouldFixBeforeUnload(assignedExpression, nodeReturnsSomething)
) {
extra = extraMessages.beforeunload;
} else if (eventTypeName === 'message') {
// Disable `onmessage` fix, see #537
extra = extraMessages.message;
} else {
fix = fixer => fixCode(fixer, context.getSourceCode(), node, memberExpression);
}
context.report({
node,
message,
data: {
replacement,
method: eventMethodName,
extra: extra ? ` ${extra}` : ''
},
fix
});
}
};
};
const schema = [
{
type: 'object',
properties: {
excludedPackages: {
type: 'array',
items: {
type: 'string'
},
uniqueItems: true
}
},
additionalProperties: false
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema
}
};
-62
View File
@@ -1,62 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const isValidVariableName = require('./utils/is-valid-variable-name');
const quoteString = require('./utils/quote-string');
const methodSelector = require('./utils/method-selector');
const selector = [
methodSelector({
name: 'setAttribute',
length: 2
}),
'[arguments.0.type="Literal"]'
].join('');
const parseNodeText = (context, argument) => context.getSourceCode().getText(argument);
const dashToCamelCase = string => string.replace(/-[a-z]/g, s => s[1].toUpperCase());
const fix = (context, node, fixer) => {
let [name, value] = node.arguments;
const calleeObject = parseNodeText(context, node.callee.object);
name = dashToCamelCase(name.value.slice(5));
value = parseNodeText(context, value);
const replacement = `${calleeObject}.dataset${
isValidVariableName(name) ?
`.${name}` :
`[${quoteString(name)}]`
} = ${value}`;
return fixer.replaceText(node, replacement);
};
const create = context => {
return {
[selector](node) {
const name = node.arguments[0].value;
if (typeof name !== 'string' || !name.startsWith('data-') || name === 'data-') {
return;
}
context.report({
node,
message: 'Prefer `.dataset` over `setAttribute(…)`.',
fix: fixer => fix(context, node, fixer)
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-227
View File
@@ -1,227 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const quoteString = require('./utils/quote-string');
const keys = new Set([
'keyCode',
'charCode',
'which'
]);
// https://github.com/facebook/react/blob/b87aabd/packages/react-dom/src/events/getEventKey.js#L36
// Only meta characters which can't be deciphered from `String.fromCharCode()`
const translateToKey = {
8: 'Backspace',
9: 'Tab',
12: 'Clear',
13: 'Enter',
16: 'Shift',
17: 'Control',
18: 'Alt',
19: 'Pause',
20: 'CapsLock',
27: 'Escape',
32: ' ',
33: 'PageUp',
34: 'PageDown',
35: 'End',
36: 'Home',
37: 'ArrowLeft',
38: 'ArrowUp',
39: 'ArrowRight',
40: 'ArrowDown',
45: 'Insert',
46: 'Delete',
112: 'F1',
113: 'F2',
114: 'F3',
115: 'F4',
116: 'F5',
117: 'F6',
118: 'F7',
119: 'F8',
120: 'F9',
121: 'F10',
122: 'F11',
123: 'F12',
144: 'NumLock',
145: 'ScrollLock',
186: ';',
187: '=',
188: ',',
189: '-',
190: '.',
191: '/',
219: '[',
220: '\\',
221: ']',
222: '\'',
224: 'Meta'
};
const isPropertyNamedAddEventListener = node =>
node &&
node.type === 'CallExpression' &&
node.callee &&
node.callee.type === 'MemberExpression' &&
node.callee.property &&
node.callee.property.name === 'addEventListener';
const getEventNodeAndReferences = (context, node) => {
const eventListener = getMatchingAncestorOfType(node, 'CallExpression', isPropertyNamedAddEventListener);
const callback = eventListener && eventListener.arguments && eventListener.arguments[1];
switch (callback && callback.type) {
case 'ArrowFunctionExpression':
case 'FunctionExpression': {
const eventVariable = context.getDeclaredVariables(callback)[0];
const references = eventVariable && eventVariable.references;
return {
event: callback.params && callback.params[0],
references
};
}
default:
return {};
}
};
const isPropertyOf = (node, eventNode) => {
return (
node &&
node.parent &&
node.parent.type === 'MemberExpression' &&
node.parent.object &&
node.parent.object === eventNode
);
};
// The third argument is a condition function, as one passed to `Array#filter()`
// Helpful if nearest node of type also needs to have some other property
const getMatchingAncestorOfType = (node, type, fn = () => true) => {
let current = node;
while (current) {
if (current.type === type && fn(current)) {
return current;
}
current = current.parent;
}
};
const getParentByLevel = (node, level) => {
let current = node;
while (current && level) {
level--;
current = current.parent;
}
/* istanbul ignore else */
if (level === 0) {
return current;
}
};
const fix = node => fixer => {
// Since we're only fixing direct property access usages, like `event.keyCode`
const nearestIf = getParentByLevel(node, 3);
if (!nearestIf || nearestIf.type !== 'IfStatement') {
return;
}
const {right = {}, operator} = nearestIf.test;
const isTestingEquality = operator === '==' || operator === '===';
const isRightValid = isTestingEquality && right.type === 'Literal' && typeof right.value === 'number';
// Either a meta key or a printable character
const keyCode = translateToKey[right.value] || String.fromCharCode(right.value);
// And if we recognize the `.keyCode`
if (!isRightValid || !keyCode) {
return;
}
// Apply fixes
return [
fixer.replaceText(node, 'key'),
fixer.replaceText(right, quoteString(keyCode))
];
};
const create = context => {
const report = node => {
context.report({
message: `Use \`.key\` instead of \`.${node.name}\``,
node,
fix: fix(node)
});
};
return {
'Identifier:matches([name="keyCode"], [name="charCode"], [name="which"])'(node) {
// Normal case when usage is direct -> `event.keyCode`
const {event, references} = getEventNodeAndReferences(context, node);
if (!event) {
return;
}
if (
references &&
references.find(reference => isPropertyOf(node, reference.identifier))
) {
report(node);
}
},
Property(node) {
// Destructured case
const propertyName = node.value && node.value.name;
if (!keys.has(propertyName)) {
return;
}
const {event, references} = getEventNodeAndReferences(context, node);
if (!event) {
return;
}
const nearestVariableDeclarator = getMatchingAncestorOfType(
node,
'VariableDeclarator'
);
const initObject =
nearestVariableDeclarator &&
nearestVariableDeclarator.init &&
nearestVariableDeclarator.init;
// Make sure initObject is a reference of eventVariable
if (
references &&
references.find(reference => reference.identifier === initObject)
) {
report(node.value);
return;
}
// When the event parameter itself is destructured directly
const isEventParameterDestructured = event.type === 'ObjectPattern';
if (isEventParameterDestructured) {
// Check for properties
for (const property of event.properties) {
if (property === node) {
report(node.value);
}
}
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
@@ -1,19 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const create = () => ({});
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
},
deprecated: true,
replacedBy: [
'prefer-exponentiation-operator'
]
};
-179
View File
@@ -1,179 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const isMethodNamed = require('./utils/is-method-named');
const MESSAGE_ID_FLATMAP = 'flat-map';
const MESSAGE_ID_SPREAD = 'spread';
const SELECTOR_SPREAD = [
// [].concat(...bar.map((i) => i))
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
'CallExpression',
// [].concat(...bar.map((i) => i))
// ^^
'[callee.object.type="ArrayExpression"]',
'[callee.object.elements.length=0]',
// [].concat(...bar.map((i) => i))
// ^^^^^^
'[callee.type="MemberExpression"]',
'[callee.computed=false]',
'[callee.property.type="Identifier"]',
'[callee.property.name="concat"]',
// [].concat(...bar.map((i) => i))
// ^^^^^^^^^^^^^^^^^^^^
'[arguments.0.type="SpreadElement"]',
// [].concat(...bar.map((i) => i))
// ^^^^^^^^^^^^^^^^^
'[arguments.0.argument.type="CallExpression"]',
// [].concat(...bar.map((i) => i))
// ^^^^^^^
'[arguments.0.argument.callee.type="MemberExpression"]',
'[arguments.0.argument.callee.computed=false]',
// [].concat(...bar.map((i) => i))
// ^^^
'[arguments.0.argument.callee.property.type="Identifier"]',
'[arguments.0.argument.callee.property.name="map"]'
].join('');
const reportFlatMap = (context, nodeFlat, nodeMap) => {
const source = context.getSourceCode();
// Node covers:
// map(…).flat();
// ^^^^
// (map(…)).flat();
// ^^^^
const flatIdentifer = nodeFlat.callee.property;
// Location will be:
// map(…).flat();
// ^
// (map(…)).flat();
// ^
const dot = source.getTokenBefore(flatIdentifer);
// Location will be:
// map(…).flat();
// ^
// (map(…)).flat();
// ^
const maybeSemicolon = source.getTokenAfter(nodeFlat);
const hasSemicolon = Boolean(maybeSemicolon) && maybeSemicolon.value === ';';
// Location will be:
// (map(…)).flat();
// ^
const tokenBetween = source.getLastTokenBetween(nodeMap, dot);
// Location will be:
// map(…).flat();
// ^
// (map(…)).flat();
// ^
const beforeSemicolon = tokenBetween || nodeMap;
// Location will be:
// map(…).flat();
// ^
// (map(…)).flat();
// ^
const fixEnd = nodeFlat.range[1];
// Location will be:
// map(…).flat();
// ^
// (map(…)).flat();
// ^
const fixStart = dot.range[0];
const mapProperty = nodeMap.callee.property;
context.report({
node: nodeFlat,
messageId: MESSAGE_ID_FLATMAP,
fix: fixer => {
const fixings = [
// Removes:
// map(…).flat();
// ^^^^^^^
// (map(…)).flat();
// ^^^^^^^
fixer.removeRange([fixStart, fixEnd]),
// Renames:
// map(…).flat();
// ^^^
// (map(…)).flat();
// ^^^
fixer.replaceText(mapProperty, 'flatMap')
];
if (hasSemicolon) {
// Moves semicolon to:
// map(…).flat();
// ^
// (map(…)).flat();
// ^
fixings.push(fixer.insertTextAfter(beforeSemicolon, ';'));
fixings.push(fixer.remove(maybeSemicolon));
}
return fixings;
}
});
};
const create = context => ({
CallExpression: node => {
if (!isMethodNamed(node, 'flat')) {
return;
}
if (node.arguments.length > 1) {
return;
}
if (
node.arguments.length === 1 &&
node.arguments[0].type === 'Literal' &&
node.arguments[0].value !== 1
) {
return;
}
const parent = node.callee.object;
if (!isMethodNamed(parent, 'map')) {
return;
}
reportFlatMap(context, node, parent);
},
[SELECTOR_SPREAD]: node => {
context.report({
node,
messageId: MESSAGE_ID_SPREAD
});
}
});
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages: {
[MESSAGE_ID_FLATMAP]: 'Prefer `.flatMap(…)` over `.map(…).flat()`.',
[MESSAGE_ID_SPREAD]: 'Prefer `.flatMap(…)` over `[].concat(...foo.map(…))`.'
}
}
};
-81
View File
@@ -1,81 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const isMethodNamed = require('./utils/is-method-named');
const isLiteralValue = require('./utils/is-literal-value');
const message = 'Use `.includes()`, rather than `.indexOf()`, when checking for existence.';
// Ignore {_,lodash,underscore}.indexOf
const ignoredVariables = new Set(['_', 'lodash', 'underscore']);
const isIgnoredTarget = node => node.type === 'Identifier' && ignoredVariables.has(node.name);
const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1;
const isLiteralZero = node => isLiteralValue(node, 0);
const isNegativeResult = node => ['===', '==', '<'].includes(node.operator);
const report = (context, node, target, argumentsNodes) => {
const sourceCode = context.getSourceCode();
const memberExpressionNode = target.parent;
const dotToken = sourceCode.getTokenBefore(memberExpressionNode.property);
const targetSource = sourceCode.getText().slice(memberExpressionNode.range[0], dotToken.range[0]);
// Strip default `fromIndex`
if (isLiteralZero(argumentsNodes[1])) {
argumentsNodes = argumentsNodes.slice(0, 1);
}
const argumentsSource = argumentsNodes.map(argument => sourceCode.getText(argument));
context.report({
node,
message,
fix: fixer => {
const replacement = `${isNegativeResult(node) ? '!' : ''}${targetSource}.includes(${argumentsSource.join(', ')})`;
return fixer.replaceText(node, replacement);
}
});
};
const create = context => ({
BinaryExpression: node => {
const {left, right} = node;
if (!isMethodNamed(left, 'indexOf')) {
return;
}
const target = left.callee.object;
if (isIgnoredTarget(target)) {
return;
}
const {arguments: argumentsNodes} = left;
// Ignore something.indexOf(foo, 0, another)
if (argumentsNodes.length > 2) {
return;
}
if (
(['!==', '!=', '>', '===', '=='].includes(node.operator) && isNegativeOne(right)) ||
(['>=', '<'].includes(node.operator) && isLiteralZero(right))
) {
report(
context,
node,
target,
argumentsNodes
);
}
}
});
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-136
View File
@@ -1,136 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const isValueNotUsable = require('./utils/is-value-not-usable');
const methodSelector = require('./utils/method-selector');
const messages = {
replaceChildOrInsertBefore:
'Prefer `{{oldChildNode}}.{{preferredMethod}}({{newChildNode}})` over `{{parentNode}}.{{method}}({{newChildNode}}, {{oldChildNode}})`.',
insertAdjacentTextOrInsertAdjacentElement:
'Prefer `{{reference}}.{{preferredMethod}}({{content}})` over `{{reference}}.{{method}}({{position}}, {{content}})`.'
};
const replaceChildOrInsertBeforeSelector = [
methodSelector({
names: ['replaceChild', 'insertBefore'],
length: 2
}),
// We only allow Identifier for now
'[arguments.0.type="Identifier"]',
'[arguments.0.name!="undefined"]',
'[arguments.1.type="Identifier"]',
'[arguments.1.name!="undefined"]',
// This check makes sure that only the first method of chained methods with same identifier name e.g: parentNode.insertBefore(alfa, beta).insertBefore(charlie, delta); gets reported
'[callee.object.type="Identifier"]'
].join('');
const forbiddenMethods = new Map([
['replaceChild', 'replaceWith'],
['insertBefore', 'before']
]);
const checkForReplaceChildOrInsertBefore = (context, node) => {
const method = node.callee.property.name;
const parentNode = node.callee.object.name;
const [newChildNode, oldChildNode] = node.arguments.map(({name}) => name);
const preferredMethod = forbiddenMethods.get(method);
const fix = isValueNotUsable(node) ?
fixer => fixer.replaceText(
node,
`${oldChildNode}.${preferredMethod}(${newChildNode})`
) :
undefined;
return context.report({
node,
messageId: 'replaceChildOrInsertBefore',
data: {
parentNode,
method,
preferredMethod,
newChildNode,
oldChildNode
},
fix
});
};
const insertAdjacentTextOrInsertAdjacentElementSelector = [
methodSelector({
names: ['insertAdjacentText', 'insertAdjacentElement'],
length: 2
}),
// Position argument should be `string`
'[arguments.0.type="Literal"]',
// TODO: remove this limits on second argument
':matches([arguments.1.type="Literal"], [arguments.1.type="Identifier"])',
// TODO: remove this limits on callee
'[callee.object.type="Identifier"]'
].join('');
const positionReplacers = new Map([
['beforebegin', 'before'],
['afterbegin', 'prepend'],
['beforeend', 'append'],
['afterend', 'after']
]);
const checkForInsertAdjacentTextOrInsertAdjacentElement = (context, node) => {
const method = node.callee.property.name;
const [positionNode, contentNode] = node.arguments;
const position = positionNode.value;
// Return early when specified position value of first argument is not a recognized value.
if (!positionReplacers.has(position)) {
return;
}
const preferredMethod = positionReplacers.get(position);
const content = context.getSource(contentNode);
const reference = context.getSource(node.callee.object);
const fix = method === 'insertAdjacentElement' && !isValueNotUsable(node) ?
undefined :
// TODO: make a better fix, don't touch reference
fixer => fixer.replaceText(
node,
`${reference}.${preferredMethod}(${content})`
);
return context.report({
node,
messageId: 'insertAdjacentTextOrInsertAdjacentElement',
data: {
reference,
method,
preferredMethod,
position: context.getSource(positionNode),
content
},
fix
});
};
const create = context => {
return {
[replaceChildOrInsertBeforeSelector](node) {
checkForReplaceChildOrInsertBefore(context, node);
},
[insertAdjacentTextOrInsertAdjacentElementSelector](node) {
checkForInsertAdjacentTextOrInsertAdjacentElement(context, node);
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages
}
};
-312
View File
@@ -1,312 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const isLiteralValue = require('./utils/is-literal-value');
const methods = new Map([
[
'slice',
{
argumentsIndexes: [0, 1],
supportObjects: new Set([
'Array',
'String',
'ArrayBuffer',
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
'BigInt64Array',
'BigUint64Array'
// `{Blob,File}#slice()` are not generally used
// 'Blob'
// 'File'
])
}
],
[
'splice',
{
argumentsIndexes: [0],
supportObjects: new Set([
'Array'
])
}
]
]);
const OPERATOR_MINUS = '-';
const isPropertiesEqual = (node1, node2) => properties => {
return properties.every(property => isEqual(node1[property], node2[property]));
};
const isTemplateElementEqual = (node1, node2) => {
return (
node1.value &&
node2.value &&
node1.tail === node2.tail &&
isPropertiesEqual(node1.value, node2.value)(['cooked', 'raw'])
);
};
const isTemplateLiteralEqual = (node1, node2) => {
const {quasis: quasis1} = node1;
const {quasis: quasis2} = node2;
return (
quasis1.length === quasis2.length &&
quasis1.every((templateElement, index) =>
isEqual(templateElement, quasis2[index])
)
);
};
const isEqual = (node1, node2) => {
if (node1 === node2) {
return true;
}
const compare = isPropertiesEqual(node1, node2);
if (!compare(['type'])) {
return false;
}
const {type} = node1;
switch (type) {
case 'Identifier':
return compare(['name', 'computed']);
case 'Literal':
return compare(['value', 'raw']);
case 'TemplateLiteral':
return isTemplateLiteralEqual(node1, node2);
case 'TemplateElement':
return isTemplateElementEqual(node1, node2);
case 'BinaryExpression':
return compare(['operator', 'left', 'right']);
case 'MemberExpression':
return compare(['object', 'property']);
default:
return false;
}
};
const isLengthMemberExpression = node => node &&
node.type === 'MemberExpression' &&
node.property &&
node.property.type === 'Identifier' &&
node.property.name === 'length' &&
node.object;
const isLiteralPositiveValue = node =>
node &&
node.type === 'Literal' &&
typeof node.value === 'number' &&
node.value > 0;
const getLengthMemberExpression = node => {
if (!node) {
return;
}
const {type, operator, left, right} = node;
if (
type !== 'BinaryExpression' ||
operator !== OPERATOR_MINUS ||
!left ||
!isLiteralPositiveValue(right)
) {
return;
}
if (isLengthMemberExpression(left)) {
return left;
}
// Nested BinaryExpression
return getLengthMemberExpression(left);
};
const getRemoveAbleNode = (target, argument) => {
const lengthMemberExpression = getLengthMemberExpression(argument);
if (
lengthMemberExpression &&
isEqual(target, lengthMemberExpression.object)
) {
return lengthMemberExpression;
}
};
const getRemovalRange = (node, sourceCode) => {
let before = sourceCode.getTokenBefore(node);
let after = sourceCode.getTokenAfter(node);
let [start, end] = node.range;
let hasParentheses = true;
while (hasParentheses) {
hasParentheses =
before.type === 'Punctuator' &&
before.value === '(' &&
after.type === 'Punctuator' &&
after.value === ')';
if (hasParentheses) {
before = sourceCode.getTokenBefore(before);
after = sourceCode.getTokenAfter(after);
start = before.range[1];
end = after.range[0];
}
}
const [nextStart] = after.range;
const textBetween = sourceCode.text.slice(end, nextStart);
end += textBetween.match(/\S|$/).index;
return [start, end];
};
const getMemberName = node => {
const {type, property} = node;
if (
type === 'MemberExpression' &&
property &&
property.type === 'Identifier'
) {
return property.name;
}
};
function parse(node) {
const {callee, arguments: originalArguments} = node;
let method = callee.property.name;
let target = callee.object;
let argumentsNodes = originalArguments;
if (methods.has(method)) {
return {
method,
target,
argumentsNodes
};
}
if (method !== 'call' && method !== 'apply') {
return;
}
const isApply = method === 'apply';
method = getMemberName(callee.object);
if (!methods.has(method)) {
return;
}
const {supportObjects} = methods.get(method);
const parentCallee = callee.object.object;
if (
// [].{slice,splice}
(
parentCallee.type === 'ArrayExpression' &&
parentCallee.elements.length === 0
) ||
// ''.slice
(
method === 'slice' &&
isLiteralValue(parentCallee, '')
) ||
// {Array,String...}.prototype.slice
// Array.prototype.splice
(
getMemberName(parentCallee) === 'prototype' &&
parentCallee.object.type === 'Identifier' &&
supportObjects.has(parentCallee.object.name)
)
) {
[target] = originalArguments;
if (isApply) {
const [, secondArgument] = originalArguments;
if (!secondArgument || secondArgument.type !== 'ArrayExpression') {
return;
}
argumentsNodes = secondArgument.elements;
} else {
argumentsNodes = originalArguments.slice(1);
}
return {
method,
target,
argumentsNodes
};
}
}
const create = context => ({
CallExpression: node => {
if (node.callee.type !== 'MemberExpression') {
return;
}
const parsed = parse(node);
if (!parsed) {
return;
}
const {
method,
target,
argumentsNodes
} = parsed;
const {argumentsIndexes} = methods.get(method);
const removableNodes = argumentsIndexes
.map(index => getRemoveAbleNode(target, argumentsNodes[index]))
.filter(Boolean);
if (removableNodes.length === 0) {
return;
}
context.report({
node,
message: `Prefer negative index over length minus index for \`${method}\`.`,
fix(fixer) {
const sourceCode = context.getSourceCode();
return removableNodes.map(
node => fixer.removeRange(
getRemovalRange(node, sourceCode)
)
);
}
});
}
});
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-42
View File
@@ -1,42 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const isValueNotUsable = require('./utils/is-value-not-usable');
const methodSelector = require('./utils/method-selector');
const {notDomNodeSelector} = require('./utils/not-dom-node');
const message = 'Prefer `Node#append()` over `Node#appendChild()`.';
const selector = [
methodSelector({
name: 'appendChild',
length: 1
}),
notDomNodeSelector('callee.object'),
notDomNodeSelector('arguments.0')
].join('');
const create = context => {
return {
[selector](node) {
const fix = isValueNotUsable(node) ?
fixer => fixer.replaceText(node.callee.property, 'append') :
undefined;
context.report({
node,
message,
fix
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-76
View File
@@ -1,76 +0,0 @@
'use strict';
const {isParenthesized, hasSideEffect} = require('eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url');
const methodSelector = require('./utils/method-selector');
const {notDomNodeSelector} = require('./utils/not-dom-node');
const needsSemicolon = require('./utils/needs-semicolon');
const isValueNotUsable = require('./utils/is-value-not-usable');
const selector = [
methodSelector({
name: 'removeChild',
length: 1
}),
notDomNodeSelector('callee.object'),
notDomNodeSelector('arguments.0')
].join('');
const ERROR_MESSAGE_ID = 'error';
const SUGGESTION_MESSAGE_ID = 'suggestion';
const create = context => {
const sourceCode = context.getSourceCode();
return {
[selector](node) {
const parentNode = node.callee.object;
const childNode = node.arguments[0];
const problem = {
node,
messageId: ERROR_MESSAGE_ID
};
const fix = fixer => {
let childNodeText = sourceCode.getText(childNode);
if (isParenthesized(childNode, sourceCode) || childNode.type === 'AwaitExpression') {
childNodeText = `(${childNodeText})`;
}
if (needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, childNodeText)) {
childNodeText = `;${childNodeText}`;
}
return fixer.replaceText(node, `${childNodeText}.remove()`);
};
if (!hasSideEffect(parentNode, sourceCode) && isValueNotUsable(node)) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: SUGGESTION_MESSAGE_ID,
fix
}
];
}
context.report(problem);
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages: {
[ERROR_MESSAGE_ID]: 'Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`.',
[SUGGESTION_MESSAGE_ID]: 'Replace `parentNode.removeChild(childNode)` with `childNode.remove()`.'
}
}
};
-114
View File
@@ -1,114 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const isShadowed = require('./utils/is-shadowed');
const renameIdentifier = require('./utils/rename-identifier');
const METHOD_ERROR_MESSAGE_ID = 'method-error';
const METHOD_SUGGESTION_MESSAGE_ID = 'method-suggestion';
const PROPERTY_ERROR_MESSAGE_ID = 'property-error';
const methods = {
// Safe
parseInt: true,
parseFloat: true,
// Unsafe
isNaN: false,
isFinite: false
};
const methodsSelector = [
'CallExpression',
'>',
'Identifier.callee',
`:matches(${Object.keys(methods).map(name => `[name="${name}"]`).join(', ')})`
].join('');
const propertiesSelector = [
'Identifier',
'[name="NaN"]',
`:not(${
[
'MemberExpression[computed=false] > Identifier.property',
'FunctionDeclaration > Identifier.id',
'ClassDeclaration > Identifier.id',
'MethodDefinition > Identifier.key',
'VariableDeclarator > Identifier.id',
'Property[shorthand=false] > Identifier.key',
'TSDeclareFunction > Identifier.id',
'TSEnumMember > Identifier.id',
'TSPropertySignature > Identifier.key'
].join(', ')
})`
].join('');
const create = context => {
const sourceCode = context.getSourceCode();
return {
[methodsSelector]: node => {
if (isShadowed(context.getScope(), node)) {
return;
}
const {name} = node;
const isSafe = methods[name];
const problem = {
node,
messageId: METHOD_ERROR_MESSAGE_ID,
data: {
name
}
};
const fix = fixer => renameIdentifier(node, `Number.${name}`, fixer, sourceCode);
if (isSafe) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: METHOD_SUGGESTION_MESSAGE_ID,
data: {
name
},
fix
}
];
}
context.report(problem);
},
[propertiesSelector]: node => {
if (isShadowed(context.getScope(), node)) {
return;
}
const {name} = node;
context.report({
node,
messageId: PROPERTY_ERROR_MESSAGE_ID,
data: {
name
},
fix: fixer => renameIdentifier(node, `Number.${name}`, fixer, sourceCode)
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages: {
[METHOD_ERROR_MESSAGE_ID]: 'Prefer `Number.{{name}}()` over `{{name}}()`.',
[METHOD_SUGGESTION_MESSAGE_ID]: 'Replace `{{name}}()` with `Number.{{name}}()`.',
[PROPERTY_ERROR_MESSAGE_ID]: 'Prefer `Number.{{name}}` over `{{name}}`.'
}
}
};
-134
View File
@@ -1,134 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const forbiddenIdentifierNames = new Map([
['getElementById', 'querySelector'],
['getElementsByClassName', 'querySelectorAll'],
['getElementsByTagName', 'querySelectorAll']
]);
const getReplacementForId = value => `#${value}`;
const getReplacementForClass = value => value.match(/\S+/g).map(className => `.${className}`).join('');
const getQuotedReplacement = (node, value) => {
const leftQuote = node.raw.charAt(0);
const rightQuote = node.raw.charAt(node.raw.length - 1);
return `${leftQuote}${value}${rightQuote}`;
};
const getLiteralFix = (fixer, node, identifierName) => {
let replacement = node.raw;
if (identifierName === 'getElementById') {
replacement = getQuotedReplacement(node, getReplacementForId(node.value));
}
if (identifierName === 'getElementsByClassName') {
replacement = getQuotedReplacement(node, getReplacementForClass(node.value));
}
return [fixer.replaceText(node, replacement)];
};
const getTemplateLiteralFix = (fixer, node, identifierName) => {
const fix = [
fixer.insertTextAfter(node, '`'),
fixer.insertTextBefore(node, '`')
];
node.quasis.forEach(templateElement => {
if (identifierName === 'getElementById') {
fix.push(
fixer.replaceText(
templateElement,
getReplacementForId(templateElement.value.cooked)
)
);
}
if (identifierName === 'getElementsByClassName') {
fix.push(
fixer.replaceText(
templateElement,
getReplacementForClass(templateElement.value.cooked)
)
);
}
});
return fix;
};
const canBeFixed = node => {
if (node.type === 'Literal') {
return node.value === null || Boolean(node.value.trim());
}
if (node.type === 'TemplateLiteral') {
return (
node.expressions.length === 0 &&
node.quasis.some(templateElement => templateElement.value.cooked.trim())
);
}
return false;
};
const hasValue = node => {
if (node.type === 'Literal') {
return node.value;
}
return true;
};
const fix = (node, identifierName, preferedSelector) => {
const nodeToBeFixed = node.arguments[0];
if (identifierName === 'getElementsByTagName' || !hasValue(nodeToBeFixed)) {
return fixer => fixer.replaceText(node.callee.property, preferedSelector);
}
const getArgumentFix = nodeToBeFixed.type === 'Literal' ? getLiteralFix : getTemplateLiteralFix;
return fixer => [
...getArgumentFix(fixer, nodeToBeFixed, identifierName),
fixer.replaceText(node.callee.property, preferedSelector)
];
};
const create = context => {
return {
CallExpression(node) {
const {callee: {property, type}} = node;
if (!property || type !== 'MemberExpression') {
return;
}
const identifierName = property.name;
const preferedSelector = forbiddenIdentifierNames.get(identifierName);
if (!preferedSelector) {
return;
}
const report = {
node,
message: `Prefer \`.${preferedSelector}()\` over \`.${identifierName}()\`.`
};
if (canBeFixed(node.arguments[0])) {
report.fix = fix(node, identifierName, preferedSelector);
}
context.report(report);
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-88
View File
@@ -1,88 +0,0 @@
'use strict';
const astUtils = require('eslint-ast-utils');
const getDocumentationUrl = require('./utils/get-documentation-url');
const isLiteralValue = require('./utils/is-literal-value');
const isApplySignature = (argument1, argument2) => (
// Please remove this file from `test/lint/lint.js` when fixing `null` issue
(isLiteralValue(argument1, null) ||
argument1.type === 'ThisExpression') &&
(argument2.type === 'ArrayExpression' ||
(argument2.type === 'Identifier' &&
argument2.name === 'arguments'))
);
const getReflectApplyCall = (sourceCode, func, receiver, arguments_) => (
`Reflect.apply(${sourceCode.getText(func)}, ${sourceCode.getText(receiver)}, ${sourceCode.getText(arguments_)})`
);
const fixDirectApplyCall = (node, sourceCode) => {
if (
astUtils.getPropertyName(node.callee) === 'apply' &&
node.arguments.length === 2 &&
isApplySignature(node.arguments[0], node.arguments[1])
) {
return fixer => (
fixer.replaceText(
node,
getReflectApplyCall(sourceCode, node.callee.object, node.arguments[0], node.arguments[1])
)
);
}
};
const fixFunctionPrototypeCall = (node, sourceCode) => {
if (
astUtils.getPropertyName(node.callee) === 'call' &&
astUtils.getPropertyName(node.callee.object) === 'apply' &&
astUtils.getPropertyName(node.callee.object.object) === 'prototype' &&
node.callee.object.object.object &&
node.callee.object.object.object.type === 'Identifier' &&
node.callee.object.object.object.name === 'Function' &&
node.arguments.length === 3 &&
isApplySignature(node.arguments[1], node.arguments[2])
) {
return fixer => (
fixer.replaceText(
node,
getReflectApplyCall(sourceCode, node.arguments[0], node.arguments[1], node.arguments[2])
)
);
}
};
const create = context => {
return {
CallExpression: node => {
if (
!(
node.callee.type === 'MemberExpression' &&
!['Literal', 'ArrayExpression', 'ObjectExpression'].includes(node.callee.object.type)
)
) {
return;
}
const sourceCode = context.getSourceCode();
const fix = fixDirectApplyCall(node, sourceCode) || fixFunctionPrototypeCall(node, sourceCode);
if (fix) {
context.report({
node,
message: 'Prefer `Reflect.apply()` over `Function#apply()`.',
fix
});
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-70
View File
@@ -1,70 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const quoteString = require('./utils/quote-string');
const methodSelector = require('./utils/method-selector');
const selector = methodSelector({
name: 'replace',
length: 2
});
const message = 'Prefer `String#replaceAll()` over `String#replace()`.';
function isRegexWithGlobalFlag(node) {
const {type, regex} = node;
return type === 'Literal' && regex && regex.flags === 'g';
}
function isLiteralCharactersOnly(node) {
const searchPattern = node.regex.pattern;
return !/[$()*+.?[\\\]^{}]/.test(searchPattern.replace(/\\[$()*+.?[\\\]^{}]/g, ''));
}
function removeEscapeCharacters(regexString) {
let fixedString = regexString;
let index = 0;
do {
index = fixedString.indexOf('\\', index);
if (index >= 0) {
fixedString = fixedString.slice(0, index) + fixedString.slice(index + 1);
index++;
}
} while (index >= 0);
return fixedString;
}
const create = context => {
return {
[selector]: node => {
const {arguments: arguments_} = node;
const [search] = arguments_;
if (!isRegexWithGlobalFlag(search) || !isLiteralCharactersOnly(search)) {
return;
}
context.report({
node,
message,
fix: fixer =>
[
fixer.insertTextAfter(node.callee, 'All'),
fixer.replaceText(search, quoteString(removeEscapeCharacters(search.regex.pattern)))
]
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-151
View File
@@ -1,151 +0,0 @@
'use strict';
const {findVariable} = require('eslint-utils');
const getDocumentationUrl = require('./utils/get-documentation-url');
const getVariableIdentifiers = require('./utils/get-variable-identifiers');
const methodSelector = require('./utils/method-selector');
// `[]`
const arrayExpressionSelector = [
'[init.type="ArrayExpression"]'
].join('');
// `Array()`
const ArraySelector = [
'[init.type="CallExpression"]',
'[init.callee.type="Identifier"]',
'[init.callee.name="Array"]'
].join('');
// `new Array()`
const newArraySelector = [
'[init.type="NewExpression"]',
'[init.callee.type="Identifier"]',
'[init.callee.name="Array"]'
].join('');
// `Array.from()`
// `Array.of()`
const arrayStaticMethodSelector = methodSelector({
object: 'Array',
names: ['from', 'of'],
property: 'init'
});
// `array.concat()`
// `array.copyWithin()`
// `array.fill()`
// `array.filter()`
// `array.flat()`
// `array.flatMap()`
// `array.map()`
// `array.reverse()`
// `array.slice()`
// `array.sort()`
// `array.splice()`
const arrayMethodSelector = methodSelector({
names: [
'concat',
'copyWithin',
'fill',
'filter',
'flat',
'flatMap',
'map',
'reverse',
'slice',
'sort',
'splice'
],
property: 'init'
});
const selector = [
'VariableDeclaration',
// Exclude `export const foo = [];`
`:not(${
[
'ExportNamedDeclaration',
'>',
'VariableDeclaration.declaration'
].join('')
})`,
'>',
'VariableDeclarator.declarations',
`:matches(${
[
arrayExpressionSelector,
ArraySelector,
newArraySelector,
arrayStaticMethodSelector,
arrayMethodSelector
].join(',')
})`,
'>',
'Identifier.id'
].join('');
const MESSAGE_ID = 'preferSetHas';
const isIncludesCall = node => {
/* istanbul ignore next */
if (!node.parent || !node.parent.parent) {
return false;
}
const {type, optional, callee, arguments: parameters} = node.parent.parent;
return (
type === 'CallExpression' &&
!optional,
callee &&
callee.type === 'MemberExpression' &&
!callee.computed &&
callee.object === node &&
callee.property.type === 'Identifier' &&
callee.property.name === 'includes' &&
parameters.length === 1 &&
parameters[0].type !== 'SpreadElement'
);
};
const create = context => {
return {
[selector]: node => {
const variable = findVariable(context.getScope(), node);
const identifiers = getVariableIdentifiers(variable).filter(identifier => identifier !== node);
if (
identifiers.length === 0 ||
identifiers.some(node => !isIncludesCall(node))
) {
return;
}
context.report({
node,
messageId: MESSAGE_ID,
data: {
name: node.name
},
fix: fixer => [
fixer.insertTextBefore(node.parent.init, 'new Set('),
fixer.insertTextAfter(node.parent.init, ')'),
...identifiers.map(identifier => fixer.replaceText(identifier.parent.property, 'has'))
]
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages: {
[MESSAGE_ID]: '`{{name}}` should be a `Set`, and use `{{name}}.has()` to check existence or non-existence.'
}
}
};
-53
View File
@@ -1,53 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const methodSelector = require('./utils/method-selector');
const needsSemicolon = require('./utils/needs-semicolon');
const selector = [
methodSelector({
object: 'Array',
name: 'from',
min: 1,
max: 3
}),
// Allow `Array.from({length})`
'[arguments.0.type!="ObjectExpression"]'
].join('');
const create = context => {
const sourceCode = context.getSourceCode();
const getSource = node => sourceCode.getText(node);
return {
[selector](node) {
context.report({
node,
message: 'Prefer the spread operator over `Array.from()`.',
fix: fixer => {
const [arrayLikeArgument, mapFn, thisArgument] = node.arguments.map(node => getSource(node));
let replacement = `${
needsSemicolon(sourceCode.getTokenBefore(node), sourceCode) ? ';' : ''
}[...${arrayLikeArgument}]`;
if (mapFn) {
const mapArguments = [mapFn, thisArgument].filter(Boolean);
replacement += `.map(${mapArguments.join(', ')})`;
}
return fixer.replaceText(node, replacement);
}
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-68
View File
@@ -1,68 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const doesNotContain = (string, characters) => characters.every(character => !string.includes(character));
const isSimpleString = string => doesNotContain(
string,
['^', '$', '+', '[', '{', '(', '\\', '.', '?', '*']
);
const create = context => {
return {
CallExpression(node) {
const {callee} = node;
const {property} = callee;
if (!(property && callee.type === 'MemberExpression')) {
return;
}
const arguments_ = node.arguments;
let regex;
if (property.name === 'test' && callee.object.regex) {
({regex} = callee.object);
} else if (
property.name === 'match' &&
arguments_ &&
arguments_[0] &&
arguments_[0].regex
) {
({regex} = arguments_[0]);
} else {
return;
}
if (regex.flags && regex.flags.includes('i')) {
return;
}
const {pattern} = regex;
if (pattern.startsWith('^') && isSimpleString(pattern.slice(1))) {
context.report({
node,
message: 'Prefer `String#startsWith()` over a regex with `^`.'
});
} else if (
pattern.endsWith('$') &&
isSimpleString(pattern.slice(0, -1))
) {
context.report({
node,
message: 'Prefer `String#endsWith()` over a regex with `$`.'
});
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
}
}
};
-164
View File
@@ -1,164 +0,0 @@
'use strict';
const eslintTemplateVisitor = require('eslint-template-visitor');
const getDocumentationUrl = require('./utils/get-documentation-url');
const templates = eslintTemplateVisitor();
const objectVariable = templates.variable();
const argumentsVariable = templates.spreadVariable();
const substrCallTemplate = templates.template`${objectVariable}.substr(${argumentsVariable})`;
const substringCallTemplate = templates.template`${objectVariable}.substring(${argumentsVariable})`;
const isLiteralNumber = node => node && node.type === 'Literal' && typeof node.value === 'number';
const getNumericValue = node => {
if (isLiteralNumber(node)) {
return node.value;
}
if (node.type === 'UnaryExpression' && node.operator === '-') {
return -getNumericValue(node.argument);
}
};
// This handles cases where the argument is very likely to be a number, such as `.substring('foo'.length)`.
const isLengthProperty = node => (
node &&
node.type === 'MemberExpression' &&
node.computed === false &&
node.property.type === 'Identifier' &&
node.property.name === 'length'
);
const isLikelyNumeric = node => isLiteralNumber(node) || isLengthProperty(node);
const create = context => {
const sourceCode = context.getSourceCode();
const getNodeText = node => {
const text = sourceCode.getText(node);
const before = sourceCode.getTokenBefore(node);
const after = sourceCode.getTokenAfter(node);
if (
(before && before.type === 'Punctuator' && before.value === '(') &&
(after && after.type === 'Punctuator' && after.value === ')')
) {
return `(${text})`;
}
return text;
};
return templates.visitor({
[substrCallTemplate](node) {
const objectNode = substrCallTemplate.context.getMatch(objectVariable);
const argumentNodes = substrCallTemplate.context.getMatch(argumentsVariable);
const problem = {
node,
message: 'Prefer `String#slice()` over `String#substr()`.'
};
const firstArgument = argumentNodes[0] ? sourceCode.getText(argumentNodes[0]) : undefined;
const secondArgument = argumentNodes[1] ? sourceCode.getText(argumentNodes[1]) : undefined;
let slice;
if (argumentNodes.length === 0) {
slice = [];
} else if (argumentNodes.length === 1) {
slice = [firstArgument];
} else if (argumentNodes.length === 2) {
if (firstArgument === '0') {
slice = [firstArgument, secondArgument];
} else if (
isLiteralNumber(argumentNodes[0]) &&
isLiteralNumber(argumentNodes[1])
) {
slice = [
firstArgument,
argumentNodes[0].value + argumentNodes[1].value
];
} else if (
isLikelyNumeric(argumentNodes[0]) &&
isLikelyNumeric(argumentNodes[1])
) {
slice = [firstArgument, firstArgument + ' + ' + secondArgument];
}
}
if (slice) {
const objectText = getNodeText(objectNode);
problem.fix = fixer => fixer.replaceText(node, `${objectText}.slice(${slice.join(', ')})`);
}
context.report(problem);
},
[substringCallTemplate](node) {
const objectNode = substringCallTemplate.context.getMatch(objectVariable);
const argumentNodes = substringCallTemplate.context.getMatch(argumentsVariable);
const problem = {
node,
message: 'Prefer `String#slice()` over `String#substring()`.'
};
const firstArgument = argumentNodes[0] ? sourceCode.getText(argumentNodes[0]) : undefined;
const secondArgument = argumentNodes[1] ? sourceCode.getText(argumentNodes[1]) : undefined;
const firstNumber = argumentNodes[0] ? getNumericValue(argumentNodes[0]) : undefined;
let slice;
if (argumentNodes.length === 0) {
slice = [];
} else if (argumentNodes.length === 1) {
if (firstNumber !== undefined) {
slice = [Math.max(0, firstNumber)];
} else if (isLengthProperty(argumentNodes[0])) {
slice = [firstArgument];
} else {
slice = [`Math.max(0, ${firstArgument})`];
}
} else if (argumentNodes.length === 2) {
const secondNumber = argumentNodes[1] ? getNumericValue(argumentNodes[1]) : undefined;
if (firstNumber !== undefined && secondNumber !== undefined) {
slice = firstNumber > secondNumber ?
[Math.max(0, secondNumber), Math.max(0, firstNumber)] :
[Math.max(0, firstNumber), Math.max(0, secondNumber)];
} else if (firstNumber === 0 || secondNumber === 0) {
slice = [0, `Math.max(0, ${firstNumber === 0 ? secondArgument : firstArgument})`];
} else {
// As values aren't Literal, we can not know whether secondArgument will become smaller than the first or not, causing an issue:
// .substring(0, 2) and .substring(2, 0) returns the same result
// .slice(0, 2) and .slice(2, 0) doesn't return the same result
// There's also an issue with us now knowing whether the value will be negative or not, due to:
// .substring() treats a negative number the same as it treats a zero.
// The latter issue could be solved by wrapping all dynamic numbers in Math.max(0, <value>), but the resulting code would not be nice
}
}
if (slice) {
const objectText = getNodeText(objectNode);
problem.fix = fixer => fixer.replaceText(node, `${objectText}.slice(${slice.join(', ')})`);
}
context.report(problem);
}
});
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-35
View File
@@ -1,35 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const message = 'Prefer `.textContent` over `.innerText`.';
const selector = [
'MemberExpression',
'[computed=false]',
'>',
'Identifier.property',
'[name="innerText"]'
].join('');
const create = context => {
return {
[selector]: node => {
context.report({
node,
message,
fix: fixer => fixer.replaceText(node, 'textContent')
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-44
View File
@@ -1,44 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const methodSelector = require('./utils/method-selector');
const methods = new Map([
['trimLeft', 'trimStart'],
['trimRight', 'trimEnd']
]);
const selector = methodSelector({
names: ['trimLeft', 'trimRight'],
length: 0
});
const messages = {};
for (const [method, replacement] of methods.entries()) {
messages[method] = `Prefer \`String#${method}()\` over \`String#${replacement}()\`.`;
}
const create = context => {
return {
[selector](node) {
const {property} = node.callee;
const method = property.name;
context.report({
node,
messageId: method,
fix: fixer => fixer.replaceText(property, methods.get(method))
});
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
messages
}
};
-136
View File
@@ -1,136 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const tcIdentifiers = new Set([
'isArguments',
'isArray',
'isArrayBuffer',
'isArrayLike',
'isArrayLikeObject',
'isBigInt',
'isBoolean',
'isBuffer',
'isDate',
'isElement',
'isError',
'isFinite',
'isFunction',
'isInteger',
'isLength',
'isMap',
'isNaN',
'isNative',
'isNil',
'isNull',
'isNumber',
'isObject',
'isObjectLike',
'isPlainObject',
'isPrototypeOf',
'isRegExp',
'isSafeInteger',
'isSet',
'isString',
'isSymbol',
'isTypedArray',
'isUndefined',
'isView',
'isWeakMap',
'isWeakSet',
'isWindow',
'isXMLDoc'
]);
const tcGlobalIdentifiers = new Set([
'isNaN',
'isFinite'
]);
const isTypecheckingIdentifier = (node, callExpression, isMemberExpression) =>
callExpression !== undefined &&
callExpression.arguments.length > 0 &&
node.type === 'Identifier' &&
((isMemberExpression === true &&
tcIdentifiers.has(node.name)) ||
(isMemberExpression === false &&
tcGlobalIdentifiers.has(node.name)));
const throwsErrorObject = node =>
node.argument.type === 'NewExpression' &&
node.argument.callee.type === 'Identifier' &&
node.argument.callee.name === 'Error';
const isLone = node => node.parent && node.parent.body && node.parent.body.length === 1;
const isTypecheckingMemberExpression = (node, callExpression) => {
if (isTypecheckingIdentifier(node.property, callExpression, true)) {
return true;
}
if (node.object.type === 'MemberExpression') {
return isTypecheckingMemberExpression(node.object, callExpression);
}
return false;
};
const isTypecheckingExpression = (node, callExpression) => {
switch (node.type) {
case 'Identifier':
return isTypecheckingIdentifier(node, callExpression, false);
case 'MemberExpression':
return isTypecheckingMemberExpression(node, callExpression);
case 'CallExpression':
return isTypecheckingExpression(node.callee, node);
case 'UnaryExpression':
return (
node.operator === 'typeof' ||
(node.operator === '!' && isTypecheckingExpression(node.argument))
);
case 'BinaryExpression':
return (
node.operator === 'instanceof' ||
isTypecheckingExpression(node.left, callExpression) ||
isTypecheckingExpression(node.right, callExpression)
);
case 'LogicalExpression':
return (
isTypecheckingExpression(node.left, callExpression) &&
isTypecheckingExpression(node.right, callExpression)
);
default:
return false;
}
};
const isTypechecking = node => node.type === 'IfStatement' && isTypecheckingExpression(node.test);
const create = context => {
return {
ThrowStatement: node => {
if (
throwsErrorObject(node) &&
isLone(node) &&
node.parent.parent &&
isTypechecking(node.parent.parent)
) {
context.report({
node,
message: '`new Error()` is too unspecific for a type check. Use `new TypeError()` instead.',
fix: fixer => fixer.replaceText(node.argument.callee, 'TypeError')
});
}
}
};
};
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-801
View File
@@ -1,801 +0,0 @@
'use strict';
const path = require('path');
const astUtils = require('eslint-ast-utils');
const {defaultsDeep, upperFirst, lowerFirst} = require('lodash');
const getDocumentationUrl = require('./utils/get-documentation-url');
const avoidCapture = require('./utils/avoid-capture');
const cartesianProductSamples = require('./utils/cartesian-product-samples');
const isShorthandPropertyIdentifier = require('./utils/is-shorthand-property-identifier');
const isShorthandImportIdentifier = require('./utils/is-shorthand-import-identifier');
const getVariableIdentifiers = require('./utils/get-variable-identifiers');
const renameIdentifier = require('./utils/rename-identifier');
const isUpperCase = string => string === string.toUpperCase();
const isUpperFirst = string => isUpperCase(string[0]);
// Keep this alphabetically sorted for easier maintenance
const defaultReplacements = {
acc: {
accumulator: true
},
arg: {
argument: true
},
args: {
arguments: true
},
arr: {
array: true
},
attr: {
attribute: true
},
attrs: {
attributes: true
},
btn: {
button: true
},
cb: {
callback: true
},
conf: {
config: true
},
ctx: {
context: true
},
cur: {
current: true
},
curr: {
current: true
},
db: {
database: true
},
dest: {
destination: true
},
dev: {
development: true
},
dir: {
direction: true,
directory: true
},
dirs: {
directories: true
},
doc: {
document: true
},
docs: {
documentation: true,
documents: true
},
e: {
error: true,
event: true
},
el: {
element: true
},
elem: {
element: true
},
env: {
environment: true
},
envs: {
environments: true
},
err: {
error: true
},
evt: {
event: true
},
ext: {
extension: true
},
exts: {
extensions: true
},
len: {
length: true
},
lib: {
library: true
},
mod: {
module: true
},
msg: {
message: true
},
num: {
number: true
},
obj: {
object: true
},
opts: {
options: true
},
param: {
parameter: true
},
params: {
parameters: true
},
pkg: {
package: true
},
prev: {
previous: true
},
prod: {
production: true
},
prop: {
property: true
},
props: {
properties: true
},
ref: {
reference: true
},
refs: {
references: true
},
rel: {
related: true,
relationship: true,
relative: true
},
req: {
request: true
},
res: {
response: true,
result: true
},
ret: {
returnValue: true
},
retval: {
returnValue: true
},
sep: {
separator: true
},
src: {
source: true
},
stdDev: {
standardDeviation: true
},
str: {
string: true
},
tbl: {
table: true
},
temp: {
temporary: true
},
tit: {
title: true
},
tmp: {
temporary: true
},
val: {
value: true
}
};
const defaultWhitelist = {
// React PropTypes
// https://reactjs.org/docs/typechecking-with-proptypes.html
propTypes: true,
// React.Component Class property
// https://reactjs.org/docs/react-component.html#defaultprops
defaultProps: true,
// React.Component static method
// https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
getDerivedStateFromProps: true,
// Ember class name
// https://api.emberjs.com/ember/3.10/classes/Ember.EmberENV/properties
EmberENV: true,
// `package.json` field
// https://docs.npmjs.com/specifying-dependencies-and-devdependencies-in-a-package-json-file
devDependencies: true,
// Jest configuration
// https://jestjs.io/docs/en/configuration#setupfilesafterenv-array
setupFilesAfterEnv: true,
// Next.js function
// https://nextjs.org/learn/basics/fetching-data-for-pages
getInitialProps: true
};
const prepareOptions = ({
checkProperties = false,
checkVariables = true,
checkDefaultAndNamespaceImports = 'internal',
checkShorthandImports = 'internal',
checkShorthandProperties = false,
checkFilenames = true,
extendDefaultReplacements = true,
replacements = {},
extendDefaultWhitelist = true,
whitelist = {}
} = {}) => {
const mergedReplacements = extendDefaultReplacements ?
defaultsDeep({}, replacements, defaultReplacements) :
replacements;
const mergedWhitelist = extendDefaultWhitelist ?
defaultsDeep({}, whitelist, defaultWhitelist) :
whitelist;
return {
checkProperties,
checkVariables,
checkDefaultAndNamespaceImports,
checkShorthandImports,
checkShorthandProperties,
checkFilenames,
replacements: new Map(
Object.entries(mergedReplacements).map(
([discouragedName, replacements]) =>
[discouragedName, new Map(Object.entries(replacements))]
)
),
whitelist: new Map(Object.entries(mergedWhitelist))
};
};
const getWordReplacements = (word, {replacements, whitelist}) => {
// Skip constants and whitelist
if (isUpperCase(word) || whitelist.get(word)) {
return [];
}
const replacement = replacements.get(lowerFirst(word)) ||
replacements.get(word) ||
replacements.get(upperFirst(word));
let wordReplacement = [];
if (replacement) {
const transform = isUpperFirst(word) ? upperFirst : lowerFirst;
wordReplacement = [...replacement.keys()]
.filter(name => replacement.get(name))
.map(name => transform(name));
}
return wordReplacement.length > 0 ? wordReplacement.sort() : [];
};
const getNameReplacements = (name, options, limit = 3) => {
const {whitelist} = options;
// Skip constants and whitelist
if (isUpperCase(name) || whitelist.get(name)) {
return {total: 0};
}
// Find exact replacements
const exactReplacements = getWordReplacements(name, options);
if (exactReplacements.length > 0) {
return {
total: exactReplacements.length,
samples: exactReplacements.slice(0, limit)
};
}
// Split words
const words = name.split(/(?=[^a-z])|(?<=[^A-Za-z])/).filter(Boolean);
let hasReplacements = false;
const combinations = words.map(word => {
const wordReplacements = getWordReplacements(word, options);
if (wordReplacements.length > 0) {
hasReplacements = true;
return wordReplacements;
}
return [word];
});
// No replacements for any word
if (!hasReplacements) {
return {total: 0};
}
const {
total,
samples
} = cartesianProductSamples(combinations, limit);
return {
total,
samples: samples.map(words => words.join(''))
};
};
const anotherNameMessage = 'A more descriptive name will do too.';
const formatMessage = (discouragedName, replacements, nameTypeText) => {
const message = [];
const {total, samples = []} = replacements;
if (total === 1) {
message.push(`The ${nameTypeText} \`${discouragedName}\` should be named \`${samples[0]}\`.`);
} else {
let replacementsText = samples
.map(replacement => `\`${replacement}\``)
.join(', ');
const omittedReplacementsCount = total - samples.length;
if (omittedReplacementsCount > 0) {
replacementsText += `, ... (${omittedReplacementsCount > 99 ? '99+' : omittedReplacementsCount} more omitted)`;
}
message.push(`Please rename the ${nameTypeText} \`${discouragedName}\`.`);
message.push(`Suggested names are: ${replacementsText}.`);
}
message.push(anotherNameMessage);
return message.join(' ');
};
const isExportedIdentifier = identifier => {
if (
identifier.parent.type === 'VariableDeclarator' &&
identifier.parent.id === identifier
) {
return (
identifier.parent.parent.type === 'VariableDeclaration' &&
identifier.parent.parent.parent.type === 'ExportNamedDeclaration'
);
}
if (
identifier.parent.type === 'FunctionDeclaration' &&
identifier.parent.id === identifier
) {
return identifier.parent.parent.type === 'ExportNamedDeclaration';
}
if (
identifier.parent.type === 'ClassDeclaration' &&
identifier.parent.id === identifier
) {
return identifier.parent.parent.type === 'ExportNamedDeclaration';
}
return false;
};
const shouldFix = variable => {
return !getVariableIdentifiers(variable).some(identifier => isExportedIdentifier(identifier));
};
const isDefaultOrNamespaceImportName = identifier => {
if (
identifier.parent.type === 'ImportDefaultSpecifier' &&
identifier.parent.local === identifier
) {
return true;
}
if (
identifier.parent.type === 'ImportNamespaceSpecifier' &&
identifier.parent.local === identifier
) {
return true;
}
if (
identifier.parent.type === 'ImportSpecifier' &&
identifier.parent.local === identifier &&
identifier.parent.imported.type === 'Identifier' &&
identifier.parent.imported.name === 'default'
) {
return true;
}
if (
identifier.parent.type === 'VariableDeclarator' &&
identifier.parent.id === identifier &&
astUtils.isStaticRequire(identifier.parent.init)
) {
return true;
}
return false;
};
const isClassVariable = variable => {
if (variable.defs.length !== 1) {
return false;
}
const [definition] = variable.defs;
return definition.type === 'ClassName';
};
const shouldReportIdentifierAsProperty = identifier => {
if (
identifier.parent.type === 'MemberExpression' &&
identifier.parent.property === identifier &&
!identifier.parent.computed &&
identifier.parent.parent.type === 'AssignmentExpression' &&
identifier.parent.parent.left === identifier.parent
) {
return true;
}
if (
identifier.parent.type === 'Property' &&
identifier.parent.key === identifier &&
!identifier.parent.computed &&
!identifier.parent.shorthand && // Shorthand properties are reported and fixed as variables
identifier.parent.parent.type === 'ObjectExpression'
) {
return true;
}
if (
identifier.parent.type === 'ExportSpecifier' &&
identifier.parent.exported === identifier &&
identifier.parent.local !== identifier // Same as shorthand properties above
) {
return true;
}
if (
identifier.parent.type === 'MethodDefinition' &&
identifier.parent.key === identifier &&
!identifier.parent.computed
) {
return true;
}
if (
identifier.parent.type === 'ClassProperty' &&
identifier.parent.key === identifier &&
!identifier.parent.computed
) {
return true;
}
return false;
};
const isInternalImport = node => {
let source = '';
if (node.type === 'Variable') {
source = node.node.init.arguments[0].value;
} else if (node.type === 'ImportBinding') {
source = node.parent.source.value;
}
return (
!source.includes('node_modules') &&
(source.startsWith('.') || source.startsWith('/'))
);
};
const create = context => {
const {ecmaVersion} = context.parserOptions;
const options = prepareOptions(context.options[0]);
const filenameWithExtension = context.getFilename();
const sourceCode = context.getSourceCode();
// A `class` declaration produces two variables in two scopes:
// the inner class scope, and the outer one (whereever the class is declared).
// This map holds the outer ones to be later processed when the inner one is encountered.
// For why this is not a eslint issue see https://github.com/eslint/eslint-scope/issues/48#issuecomment-464358754
const identifierToOuterClassVariable = new WeakMap();
const checkPossiblyWeirdClassVariable = variable => {
if (isClassVariable(variable)) {
if (variable.scope.type === 'class') { // The inner class variable
const [definition] = variable.defs;
const outerClassVariable = identifierToOuterClassVariable.get(definition.name);
if (!outerClassVariable) {
return checkVariable(variable);
}
// Create a normal-looking variable (like a `var` or a `function`)
// For which a single `variable` holds all references, unline with `class`
const combinedReferencesVariable = {
name: variable.name,
scope: variable.scope,
defs: variable.defs,
identifiers: variable.identifiers,
references: variable.references.concat(outerClassVariable.references)
};
// Call the common checker with the newly forged normalized class variable
return checkVariable(combinedReferencesVariable);
}
// The outer class variable, we save it for later, when it's inner counterpart is encountered
const [definition] = variable.defs;
identifierToOuterClassVariable.set(definition.name, variable);
return;
}
return checkVariable(variable);
};
// Holds a map from a `Scope` to a `Set` of new variable names generated by our fixer.
// Used to avoid generating duplicate names, see for instance `let errCb, errorCb` test.
const scopeToNamesGeneratedByFixer = new WeakMap();
const isSafeName = (name, scopes) => scopes.every(scope => {
const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
return !generatedNames || !generatedNames.has(name);
});
const checkVariable = variable => {
if (variable.defs.length === 0) {
return;
}
const [definition] = variable.defs;
if (isDefaultOrNamespaceImportName(definition.name)) {
if (!options.checkDefaultAndNamespaceImports) {
return;
}
if (
options.checkDefaultAndNamespaceImports === 'internal' &&
!isInternalImport(definition)
) {
return;
}
}
if (isShorthandImportIdentifier(definition.name)) {
if (!options.checkShorthandImports) {
return;
}
if (
options.checkShorthandImports === 'internal' &&
!isInternalImport(definition)
) {
return;
}
}
if (
!options.checkShorthandProperties &&
isShorthandPropertyIdentifier(definition.name)
) {
return;
}
const variableReplacements = getNameReplacements(variable.name, options);
if (variableReplacements.total === 0) {
return;
}
const scopes = variable.references.map(reference => reference.from).concat(variable.scope);
variableReplacements.samples = variableReplacements.samples.map(
name => avoidCapture(name, scopes, ecmaVersion, isSafeName)
);
const problem = {
node: definition.name,
message: formatMessage(definition.name.name, variableReplacements, 'variable')
};
if (variableReplacements.total === 1 && shouldFix(variable)) {
const [replacement] = variableReplacements.samples;
for (const scope of scopes) {
if (!scopeToNamesGeneratedByFixer.has(scope)) {
scopeToNamesGeneratedByFixer.set(scope, new Set());
}
const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
generatedNames.add(replacement);
}
problem.fix = fixer => {
return getVariableIdentifiers(variable)
.map(identifier => renameIdentifier(identifier, replacement, fixer, sourceCode));
};
}
context.report(problem);
};
const checkVariables = scope => {
scope.variables.forEach(variable => checkPossiblyWeirdClassVariable(variable));
};
const checkChildScopes = scope => {
scope.childScopes.forEach(scope => checkScope(scope));
};
const checkScope = scope => {
checkVariables(scope);
return checkChildScopes(scope);
};
return {
Identifier(node) {
if (!options.checkProperties) {
return;
}
if (node.name === '__proto__') {
return;
}
const identifierReplacements = getNameReplacements(node.name, options);
if (identifierReplacements.total === 0) {
return;
}
if (!shouldReportIdentifierAsProperty(node)) {
return;
}
const problem = {
node,
message: formatMessage(node.name, identifierReplacements, 'property')
};
context.report(problem);
},
Program(node) {
if (!options.checkFilenames) {
return;
}
if (
filenameWithExtension === '<input>' ||
filenameWithExtension === '<text>'
) {
return;
}
const extension = path.extname(filenameWithExtension);
const filename = path.basename(filenameWithExtension, extension);
const filenameReplacements = getNameReplacements(filename, options);
if (filenameReplacements.total === 0) {
return;
}
filenameReplacements.samples = filenameReplacements.samples.map(replacement => `${replacement}${extension}`);
context.report({
node,
message: formatMessage(filenameWithExtension, filenameReplacements, 'filename')
});
},
'Program:exit'() {
if (!options.checkVariables) {
return;
}
checkScope(context.getScope());
}
};
};
const schema = [
{
type: 'object',
properties: {
checkProperties: {
type: 'boolean'
},
checkVariables: {
type: 'boolean'
},
checkDefaultAndNamespaceImports: {
type: [
'boolean',
'string'
],
pattern: 'internal'
},
checkShorthandImports: {
type: [
'boolean',
'string'
],
pattern: 'internal'
},
checkShorthandProperties: {
type: 'boolean'
},
checkFilenames: {
type: 'boolean'
},
extendDefaultReplacements: {
type: 'boolean'
},
replacements: {
$ref: '#/items/0/definitions/abbreviations'
},
extendDefaultWhitelist: {
type: 'boolean'
},
whitelist: {
$ref: '#/items/0/definitions/booleanObject'
}
},
additionalProperties: false,
definitions: {
abbreviations: {
type: 'object',
additionalProperties: {
$ref: '#/items/0/definitions/replacements'
}
},
replacements: {
anyOf: [
{
enum: [
false
]
},
{
$ref: '#/items/0/definitions/booleanObject'
}
]
},
booleanObject: {
type: 'object',
additionalProperties: {
type: 'boolean'
}
}
}
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema
}
};
-19
View File
@@ -1,19 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const create = () => ({});
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
},
deprecated: true,
replacedBy: [
'unicorn/better-regex'
]
};
-185
View File
@@ -1,185 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const quoteString = require('./utils/quote-string');
const replaceTemplateElement = require('./utils/replace-template-element');
const escapeTemplateElementRaw = require('./utils/escape-template-element-raw');
const ignoredIdentifier = new Set([
'gql',
'html',
'svg'
]);
const ignoredMemberExpressionObject = new Set([
'styled'
]);
const isIgnoredTag = node => {
if (!node.parent || !node.parent.parent || !node.parent.parent.tag) {
return false;
}
const {tag} = node.parent.parent;
if (tag.type === 'Identifier' && ignoredIdentifier.has(tag.name)) {
return true;
}
if (tag.type === 'MemberExpression') {
const {object} = tag;
if (
object.type === 'Identifier' &&
ignoredMemberExpressionObject.has(object.name)
) {
return true;
}
}
return false;
};
const defaultMessage = 'Prefer `{{suggest}}` over `{{match}}`.';
const SUGGESTION_MESSAGE_ID = 'replace';
function getReplacements(patterns) {
return Object.entries(patterns)
.map(([match, options]) => {
if (typeof options === 'string') {
options = {
suggest: options
};
}
return {
match,
regex: new RegExp(match, 'gu'),
fix: true,
...options
};
});
}
const create = context => {
const {patterns} = {
patterns: {},
...context.options[0]
};
const replacements = getReplacements(patterns);
if (replacements.length === 0) {
return {};
}
return {
'Literal, TemplateElement': node => {
const {type} = node;
let string;
if (type === 'Literal') {
string = node.value;
} else if (!isIgnoredTag(node)) {
string = node.value.raw;
}
if (!string || typeof string !== 'string') {
return;
}
const replacement = replacements.find(({regex}) => regex.test(string));
if (!replacement) {
return;
}
const {fix: autoFix, message = defaultMessage, match, suggest} = replacement;
const messageData = {
match,
suggest
};
const problem = {
node,
message,
data: messageData
};
const fixed = string.replace(replacement.regex, suggest);
const fix = type === 'Literal' ?
fixer => fixer.replaceText(
node,
quoteString(fixed, node.raw[0])
) :
fixer => replaceTemplateElement(
fixer,
node,
escapeTemplateElementRaw(fixed)
);
if (autoFix) {
problem.fix = fix;
} else {
problem.suggest = [
{
messageId: SUGGESTION_MESSAGE_ID,
data: messageData,
fix
}
];
}
context.report(problem);
}
};
};
const schema = [
{
type: 'object',
properties: {
patterns: {
type: 'object',
additionalProperties: {
anyOf: [
{
type: 'string'
},
{
type: 'object',
required: [
'suggest'
],
properties: {
suggest: {
type: 'string'
},
fix: {
type: 'boolean'
// Default: true
},
message: {
type: 'string'
// Default: ''
}
},
additionalProperties: false
}
]
}}
},
additionalProperties: false
}
];
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema,
messages: {
[SUGGESTION_MESSAGE_ID]: 'Replace `{{match}}` with `{{suggest}}`.'
}
}
};
-34
View File
@@ -1,34 +0,0 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');
const selector = [
'ThrowStatement',
'>',
'CallExpression',
'[callee.type="Identifier"]'
].join('');
const customError = /^(?:[A-Z][\da-z]*)*Error$/;
const message = 'Use `new` when throwing an error.';
const create = context => ({
[selector]: node => {
if (customError.test(node.callee.name)) {
context.report({
node,
message,
fix: fixer => fixer.insertTextBefore(node, 'new ')
});
}
}
});
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code'
}
};
-104
View File
@@ -1,104 +0,0 @@
'use strict';
const reservedWords = require('reserved-words');
const resolveVariableName = require('./resolve-variable-name');
const indexifyName = (name, index) => name + '_'.repeat(index);
const scopeHasArgumentsSpecial = scope => {
while (scope) {
if (scope.taints.get('arguments')) {
return true;
}
scope = scope.upper;
}
return false;
};
const someScopeHasVariableName = (name, scopes) => scopes.some(scope => resolveVariableName(name, scope));
const someScopeIsStrict = scopes => scopes.some(scope => scope.isStrict);
const nameCollidesWithArgumentsSpecial = (name, scopes, isStrict) => {
if (name !== 'arguments') {
return false;
}
return isStrict || scopes.some(scope => scopeHasArgumentsSpecial(scope));
};
/*
Unresolved reference is probably from the global scope. We should avoid using that name.
For example, like `foo` and `bar` below.
```
function unicorn() {
return foo;
}
function unicorn() {
return function() {
return bar;
};
}
```
*/
const isUnresolvedName = (name, scopes) => scopes.some(scope =>
scope.references.some(reference => reference.identifier && reference.identifier.name === name && !reference.resolved) ||
isUnresolvedName(name, scope.childScopes)
);
const isSafeName = (name, scopes, ecmaVersion, isStrict) => {
ecmaVersion = Math.min(6, ecmaVersion); // 6 is the latest version understood by `reservedWords`
return (
!someScopeHasVariableName(name, scopes) &&
!reservedWords.check(name, ecmaVersion, isStrict) &&
!nameCollidesWithArgumentsSpecial(name, scopes, isStrict) &&
!isUnresolvedName(name, scopes)
);
};
const alwaysTrue = () => true;
/**
Rule-specific name check function.
@callback isSafe
@param {string} indexifiedName - The generated candidate name.
@param {Scope[]} scopes - The same list of scopes you pass to `avoidCapture`.
@returns {boolean} - `true` if the `indexifiedName` is ok.
*/
/**
Generates a unique name prefixed with `name` such that:
- it is not defined in any of the `scopes`,
- it is not a reserved word,
- it is not `arguments` in strict scopes (where `arguments` is not allowed),
- it does not collide with the actual `arguments` (which is always defined in function scopes).
Useful when you want to rename a variable (or create a new variable) while being sure not to shadow any other variables in the code.
@param {string} name - The desired name for a new variable.
@param {Scope[]} scopes - The list of scopes the new variable will be referenced in.
@param {number} ecmaVersion - The language version, get it from `context.parserOptions.ecmaVersion`.
@param {isSafe} [isSafe] - Rule-specific name check function.
@returns {string} - Either `name` as is, or a string like `${name}_` suffixed with underscores to make the name unique.
*/
module.exports = (name, scopes, ecmaVersion, isSafe = alwaysTrue) => {
const isStrict = someScopeIsStrict(scopes);
let index = 0;
let indexifiedName = indexifyName(name, index);
while (
!isSafeName(indexifiedName, scopes, ecmaVersion, isStrict) ||
!isSafe(indexifiedName, scopes)
) {
index++;
indexifiedName = indexifyName(name, index);
}
return indexifiedName;
};
-41
View File
@@ -1,41 +0,0 @@
'use strict';
const enforceNew = [
'Object',
'Array',
'ArrayBuffer',
'BigInt64Array',
'BigUint64Array',
'DataView',
'Date',
'Error',
'Float32Array',
'Float64Array',
'Function',
'Int8Array',
'Int16Array',
'Int32Array',
'Map',
'WeakMap',
'Set',
'WeakSet',
'Promise',
'RegExp',
'Uint8Array',
'Uint16Array',
'Uint32Array',
'Uint8ClampedArray'
];
const disallowNew = [
'BigInt',
'Boolean',
'Number',
'String',
'Symbol'
];
module.exports = {
enforceNew,
disallowNew
};
@@ -1,20 +0,0 @@
'use strict';
module.exports = (combinations, length = Infinity) => {
const total = combinations.reduce((total, {length}) => total * length, 1);
const samples = Array.from({length: Math.min(total, length)}, (_, sampleIndex) => {
let indexRemaining = sampleIndex;
return combinations.reduceRight((combination, items) => {
const {length} = items;
const index = indexRemaining % length;
indexRemaining = (indexRemaining - index) / length;
return [items[index], ...combination];
}, []);
});
return {
total,
samples
};
};
-98
View File
@@ -1,98 +0,0 @@
{
"mouse": [
"click",
"contextmenu",
"dblclick",
"mousedown",
"mouseenter",
"mouseleave",
"mousemove",
"mouseover",
"mouseout",
"mouseup"
],
"keyboard": [
"keydown",
"keypress",
"keyup"
],
"frame": [
"abort",
"beforeunload",
"error",
"hashchange",
"load",
"pageshow",
"pagehide",
"resize",
"scroll",
"unload"
],
"form": [
"blur",
"change",
"focus",
"focusin",
"focusout",
"input",
"invalid",
"reset",
"search",
"select",
"submit"
],
"drag": [
"drag",
"dragend",
"dragenter",
"dragleave",
"dragover",
"dragstart",
"drop"
],
"clipboard": [
"copy",
"cut",
"paste"
],
"print": [
"afterprint",
"beforeprint"
],
"media": [
"abort",
"canplay",
"canplaythrough",
"durationchange",
"ended",
"error",
"loadeddata",
"loadedmetadata",
"loadstart",
"pause",
"play",
"playing",
"progress",
"ratechange",
"seeked",
"seeking",
"stalled",
"suspend",
"timeupdate",
"volumechange",
"waiting"
],
"server-sent": [
"error",
"message",
"open"
],
"misc": [
"wheel",
"online",
"offline",
"show",
"toggle",
"wheel"
]
}
@@ -1,6 +0,0 @@
'use strict';
module.exports = string => string.replace(
/(?<=(?:^|[^\\])(?:\\\\)*)(?<symbol>(?:`|\$(?={)))/g,
'\\$<symbol>'
);
@@ -1,10 +0,0 @@
'use strict';
const path = require('path');
const packageJson = require('../../package');
const repoUrl = 'https://github.com/sindresorhus/eslint-plugin-unicorn';
module.exports = filename => {
const ruleName = path.basename(filename, '.js');
return `${repoUrl}/blob/v${packageJson.version}/docs/rules/${ruleName}.md`;
};
-10
View File
@@ -1,10 +0,0 @@
'use strict';
const {uniq} = require('lodash');
const getReferences = scope => uniq(
scope.references.concat(
...scope.childScopes.map(scope => getReferences(scope))
)
);
module.exports = getReferences;
@@ -1,8 +0,0 @@
'use strict';
const {uniq} = require('lodash');
// Get identifiers of given variable
module.exports = ({identifiers, references}) => uniq([
...identifiers,
...references.map(({identifier}) => identifier)
]);
@@ -1,11 +0,0 @@
'use strict';
const isSameNode = require('./is-same-node');
module.exports = identifier =>
identifier.parent.type === 'AssignmentPattern' &&
identifier.parent.left === identifier &&
identifier.parent.parent.type === 'Property' &&
isSameNode(identifier, identifier.parent.parent.key) &&
identifier.parent.parent.value === identifier.parent &&
identifier.parent.parent.shorthand;
-2
View File
@@ -1,2 +0,0 @@
'use strict';
module.exports = (node, value) => node && node.type === 'Literal' && node.value === value;
-12
View File
@@ -1,12 +0,0 @@
'use strict';
const isMethodNamed = (node, name) => {
return (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === name
);
};
module.exports = isMethodNamed;
-11
View File
@@ -1,11 +0,0 @@
'use strict';
module.exports = (node, object, method) => {
const {callee} = node;
return (
callee.type === 'MemberExpression' &&
callee.object.type === 'Identifier' &&
callee.object.name === object &&
callee.property.type === 'Identifier' &&
callee.property.name === method
);
};
-11
View File
@@ -1,11 +0,0 @@
'use strict';
module.exports = (node1, node2) =>
node1 &&
node2 &&
(
node1 === node2 ||
// In `babel-eslint` parent.key is not reference of identifier, #444
// issue https://github.com/babel/babel-eslint/issues/809
(node1.range[0] === node2.range[0] && node1.range[1] === node2.range[1])
);
-36
View File
@@ -1,36 +0,0 @@
'use strict';
const isSameNode = require('./is-same-node');
/**
* Finds the eslint-scope reference in the given scope.
* @param {Object} scope The scope to search.
* @param {ASTNode} node The identifier node.
* @returns {Reference|undefined} Returns the found reference or null if none were found.
*/
function findReference(scope, node) {
const references = scope.references
.filter(reference => isSameNode(reference.identifier, node));
if (references.length === 1) {
return references[0];
}
}
/**
* Checks if the given identifier node is shadowed in the given scope.
* @param {Object} scope The current scope.
* @param {string} node The identifier node to check
* @returns {boolean} Whether or not the name is shadowed.
*/
function isShadowed(scope, node) {
const reference = findReference(scope, node);
return (
reference &&
reference.resolved &&
reference.resolved.defs.length > 0
);
}
module.exports = isShadowed;
@@ -1,6 +0,0 @@
'use strict';
module.exports = identifier =>
identifier.parent.type === 'ExportSpecifier' &&
identifier.parent.exported.name === identifier.name &&
identifier.parent.local.name === identifier.name;
@@ -1,6 +0,0 @@
'use strict';
module.exports = identifier =>
identifier.parent.type === 'ImportSpecifier' &&
identifier.parent.imported.name === identifier.name &&
identifier.parent.local.name === identifier.name;
@@ -1,8 +0,0 @@
'use strict';
const isSameNode = require('./is-same-node');
module.exports = identifier =>
identifier.parent.type === 'Property' &&
identifier.parent.shorthand &&
isSameNode(identifier, identifier.parent.key);
@@ -1,3 +0,0 @@
'use strict';
module.exports = name => /^[$_a-z][\w$]*$/i.test(name);
@@ -1,3 +0,0 @@
'use strict';
module.exports = ({parent}) => !parent || parent.type === 'ExpressionStatement';
-65
View File
@@ -1,65 +0,0 @@
'use strict';
module.exports = options => {
const {
name,
names,
length,
object,
min,
max,
property = ''
} = {
min: 0,
max: Infinity,
...options
};
const prefix = property ? `${property}.` : '';
const selector = [
`[${prefix}type="CallExpression"]`,
`[${prefix}callee.type="MemberExpression"]`,
`[${prefix}callee.computed=false]`,
`[${prefix}callee.property.type="Identifier"]`
];
if (name) {
selector.push(`[callee.property.name="${name}"]`);
}
if (Array.isArray(names) && names.length !== 0) {
selector.push(
':matches(' +
names.map(name => `[${prefix}callee.property.name="${name}"]`).join(', ') +
')'
);
}
if (object) {
selector.push(`[${prefix}callee.object.type="Identifier"]`);
selector.push(`[${prefix}callee.object.name="${object}"]`);
}
if (typeof length === 'number') {
selector.push(`[${prefix}arguments.length=${length}]`);
}
if (min !== 0) {
selector.push(`[${prefix}arguments.length>=${min}]`);
}
if (Number.isFinite(max)) {
selector.push(`[${prefix}arguments.length<=${max}]`);
}
const maxArguments = Number.isFinite(max) ? max : length;
if (typeof maxArguments === 'number') {
// Exclude arguments with `SpreadElement` type
for (let index = 0; index < maxArguments; index += 1) {
selector.push(`[${prefix}arguments.${index}.type!="SpreadElement"]`);
}
}
return selector.join('');
};
-86
View File
@@ -1,86 +0,0 @@
'use strict';
// https://github.com/eslint/espree/blob/6b7d0b8100537dcd5c84a7fb17bbe28edcabe05d/lib/token-translator.js#L20
const tokenTypesNeedsSemicolon = new Set([
'String',
'Null',
'Boolean',
'Numeric',
'RegularExpression'
]);
const charactersMightNeedsSemicolon = new Set([
'[',
'(',
'/',
'`',
'+',
'-',
'*',
',',
'.'
]);
/**
Determines if a semicolon needs to be inserted before `code`, in order to avoid a SyntaxError.
@param {Token} tokenBefore Token before `code`.
@param {SourceCode} sourceCode
@param {String} [code] Code text to determine.
@returns {boolean} `true` if a semicolon needs to be inserted before `code`.
*/
function needsSemicolon(tokenBefore, sourceCode, code) {
if (
code === '' ||
(code && !charactersMightNeedsSemicolon.has(code.charAt(0)))
) {
return false;
}
if (!tokenBefore) {
return false;
}
const {type, value} = tokenBefore;
if (type === 'Punctuator') {
if (value === ';') {
return false;
}
if (value === ']' || value === ')') {
return true;
}
}
if (tokenTypesNeedsSemicolon.has(type)) {
return true;
}
if (type === 'Template') {
return value.endsWith('`');
}
const lastBlockNode = sourceCode.getNodeByRangeIndex(tokenBefore.range[0]);
if (lastBlockNode && lastBlockNode.type === 'ObjectExpression') {
return true;
}
if (type === 'Identifier') {
// `for...of`
if (value === 'of' && lastBlockNode && lastBlockNode.type === 'ForOfStatement') {
return false;
}
// `await`
if (value === 'await' && lastBlockNode && lastBlockNode.type === 'AwaitExpression') {
return false;
}
return true;
}
return false;
}
module.exports = needsSemicolon;
-30
View File
@@ -1,30 +0,0 @@
'use strict';
// AST Types:
// https://github.com/eslint/espree/blob/master/lib/ast-node-types.js#L18
// Only types possible to be `callee` or `argument` are listed
const impossibleNodeTypes = [
'ArrayExpression',
'ArrowFunctionExpression',
'ClassExpression',
'FunctionExpression',
'Literal',
'ObjectExpression',
'TemplateLiteral'
];
// We might need this later
/* istanbul ignore next */
const isNotDomNode = node =>
impossibleNodeTypes.includes(node.type) ||
(node.type === 'Identifier' && node.name === 'undefined');
const notDomNodeSelector = node => [
...impossibleNodeTypes.map(type => `[${node}.type!="${type}"]`),
`:not([${node}.type="Identifier"][${node}.name="undefined"])`
].join('');
module.exports = {
isNotDomNode,
notDomNodeSelector
};
-17
View File
@@ -1,17 +0,0 @@
'use strict';
/**
Escape string and wrap the result in quotes.
@param {string} string - The string to be quoted.
@param {string} quote - The quote character.
@returns {string} - The quoted and escaped string.
*/
module.exports = (string, quote = '\'') => {
const escaped = string
.replace(/\\/g, '\\\\')
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(new RegExp(quote, 'g'), `\\${quote}`);
return quote + escaped + quote;
};
-37
View File
@@ -1,37 +0,0 @@
'use strict';
const isShorthandPropertyIdentifier = require('./is-shorthand-property-identifier');
const isAssignmentPatternShorthandPropertyIdentifier = require('./is-assignment-pattern-shorthand-property-identifier');
const isShorthandImportIdentifier = require('./is-shorthand-import-identifier');
const isShorthandExportIdentifier = require('./is-shorthand-export-identifier');
function renameIdentifier(identifier, name, fixer, sourceCode) {
if (
isShorthandPropertyIdentifier(identifier) ||
isAssignmentPatternShorthandPropertyIdentifier(identifier)
) {
return fixer.replaceText(identifier, `${identifier.name}: ${name}`);
}
if (isShorthandImportIdentifier(identifier)) {
return fixer.replaceText(identifier, `${identifier.name} as ${name}`);
}
if (isShorthandExportIdentifier(identifier)) {
return fixer.replaceText(identifier, `${name} as ${identifier.name}`);
}
// `TypeParameter` default value
if (identifier.default) {
return fixer.replaceText(identifier, `${name} = ${sourceCode.getText(identifier.default)}`);
}
// `typeAnnotation`
if (identifier.typeAnnotation) {
return fixer.replaceText(identifier, `${name}${sourceCode.getText(identifier.typeAnnotation)}`);
}
return fixer.replaceText(identifier, name);
}
module.exports = renameIdentifier;
-7
View File
@@ -1,7 +0,0 @@
'use strict';
const getVariableIdentifiers = require('./get-variable-identifiers');
const renameIdentifier = require('./rename-identifier');
module.exports = (variable, name, fixer, sourceCode) =>
getVariableIdentifiers(variable)
.map(identifier => renameIdentifier(identifier, name, fixer, sourceCode));
-12
View File
@@ -1,12 +0,0 @@
'use strict';
// Replace `StringLiteral` or `TemplateLiteral` node with raw text
module.exports = (fixer, node, raw) =>
fixer.replaceTextRange(
// Ignore quotes and backticks
[
node.range[0] + 1,
node.range[1] - 1
],
raw
);
@@ -1,9 +0,0 @@
'use strict';
module.exports = (fixer, node, replacement) => {
const {range: [start, end], tail} = node;
return fixer.replaceTextRange(
[start + 1, end - (tail ? 1 : 2)],
replacement
);
};
@@ -1,20 +0,0 @@
'use strict';
/**
Finds a variable named `name` in the scope `scope` (or it's parents).
@param {string} name - The variable name to be resolve.
@param {Scope} scope - The scope to look for the variable in.
@returns {Variable?} - The found variable, if any.
*/
module.exports = (name, scope) => {
while (scope) {
const variable = scope.set.get(name);
if (variable) {
return variable;
}
scope = scope.upper;
}
};