| |
| |
| |
| |
| |
| "use strict"; |
|
|
| |
| |
| |
|
|
| const astUtils = require("./utils/ast-utils"); |
|
|
| |
| |
| |
|
|
| |
| module.exports = { |
| meta: { |
| type: "suggestion", |
|
|
| defaultOptions: [ |
| "^.+$", |
| { |
| classFields: false, |
| ignoreDestructuring: false, |
| onlyDeclarations: false, |
| properties: false, |
| }, |
| ], |
|
|
| docs: { |
| description: |
| "Require identifiers to match a specified regular expression", |
| recommended: false, |
| frozen: true, |
| url: "https://eslint.org/docs/latest/rules/id-match", |
| }, |
|
|
| schema: [ |
| { |
| type: "string", |
| }, |
| { |
| type: "object", |
| properties: { |
| properties: { |
| type: "boolean", |
| }, |
| classFields: { |
| type: "boolean", |
| }, |
| onlyDeclarations: { |
| type: "boolean", |
| }, |
| ignoreDestructuring: { |
| type: "boolean", |
| }, |
| }, |
| additionalProperties: false, |
| }, |
| ], |
| messages: { |
| notMatch: |
| "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", |
| notMatchPrivate: |
| "Identifier '#{{name}}' does not match the pattern '{{pattern}}'.", |
| }, |
| }, |
|
|
| create(context) { |
| |
| |
| |
| const [ |
| pattern, |
| { |
| classFields: checkClassFields, |
| ignoreDestructuring, |
| onlyDeclarations, |
| properties: checkProperties, |
| }, |
| ] = context.options; |
| const regexp = new RegExp(pattern, "u"); |
|
|
| const sourceCode = context.sourceCode; |
| let globalScope; |
|
|
| |
| |
| |
|
|
| |
| const reportedNodes = new Set(); |
| const ALLOWED_PARENT_TYPES = new Set([ |
| "CallExpression", |
| "NewExpression", |
| ]); |
| const DECLARATION_TYPES = new Set([ |
| "FunctionDeclaration", |
| "VariableDeclarator", |
| ]); |
| const IMPORT_TYPES = new Set([ |
| "ImportSpecifier", |
| "ImportNamespaceSpecifier", |
| "ImportDefaultSpecifier", |
| ]); |
|
|
| |
| |
| |
| |
| |
| |
| function isReferenceToGlobalVariable(node) { |
| const variable = globalScope.set.get(node.name); |
|
|
| return ( |
| variable && |
| variable.defs.length === 0 && |
| variable.references.some(ref => ref.identifier === node) |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function isInvalid(name) { |
| return !regexp.test(name); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function isInsideObjectPattern(node) { |
| let { parent } = node; |
|
|
| while (parent) { |
| if (parent.type === "ObjectPattern") { |
| return true; |
| } |
|
|
| parent = parent.parent; |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function shouldReport(effectiveParent, name) { |
| return ( |
| (!onlyDeclarations || |
| DECLARATION_TYPES.has(effectiveParent.type)) && |
| !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && |
| isInvalid(name) |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function report(node) { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (!reportedNodes.has(node.range.toString())) { |
| const messageId = |
| node.type === "PrivateIdentifier" |
| ? "notMatchPrivate" |
| : "notMatch"; |
|
|
| context.report({ |
| node, |
| messageId, |
| data: { |
| name: node.name, |
| pattern, |
| }, |
| }); |
| reportedNodes.add(node.range.toString()); |
| } |
| } |
|
|
| return { |
| Program(node) { |
| globalScope = sourceCode.getScope(node); |
| }, |
|
|
| Identifier(node) { |
| const name = node.name, |
| parent = node.parent, |
| effectiveParent = |
| parent.type === "MemberExpression" |
| ? parent.parent |
| : parent; |
|
|
| if ( |
| isReferenceToGlobalVariable(node) || |
| astUtils.isImportAttributeKey(node) |
| ) { |
| return; |
| } |
|
|
| if (parent.type === "MemberExpression") { |
| if (!checkProperties) { |
| return; |
| } |
|
|
| |
| if ( |
| parent.object.type === "Identifier" && |
| parent.object.name === name |
| ) { |
| if (isInvalid(name)) { |
| report(node); |
| } |
|
|
| |
| } else if ( |
| effectiveParent.type === "AssignmentExpression" && |
| effectiveParent.left.type === "MemberExpression" && |
| effectiveParent.left.property.name === node.name |
| ) { |
| if (isInvalid(name)) { |
| report(node); |
| } |
|
|
| |
| } else if ( |
| effectiveParent.type === "AssignmentExpression" && |
| effectiveParent.right.type !== "MemberExpression" |
| ) { |
| if (isInvalid(name)) { |
| report(node); |
| } |
| } |
|
|
| |
| } else if ( |
| parent.type === "Property" && |
| parent.parent.type === "ObjectExpression" && |
| parent.key === node && |
| !parent.computed |
| ) { |
| if (checkProperties && isInvalid(name)) { |
| report(node); |
| } |
|
|
| |
| |
| |
| |
| |
| } else if ( |
| parent.type === "Property" || |
| parent.type === "AssignmentPattern" |
| ) { |
| if ( |
| parent.parent && |
| parent.parent.type === "ObjectPattern" |
| ) { |
| if ( |
| !ignoreDestructuring && |
| parent.shorthand && |
| parent.value.left && |
| isInvalid(name) |
| ) { |
| report(node); |
| } |
|
|
| const assignmentKeyEqualsValue = |
| parent.key.name === parent.value.name; |
|
|
| |
| if (!assignmentKeyEqualsValue && parent.key === node) { |
| return; |
| } |
|
|
| const valueIsInvalid = |
| parent.value.name && isInvalid(name); |
|
|
| |
| if ( |
| valueIsInvalid && |
| !(assignmentKeyEqualsValue && ignoreDestructuring) |
| ) { |
| report(node); |
| } |
| } |
|
|
| |
| if ( |
| (!checkProperties && !parent.computed) || |
| (ignoreDestructuring && isInsideObjectPattern(node)) |
| ) { |
| return; |
| } |
|
|
| |
| if ( |
| parent.right !== node && |
| shouldReport(effectiveParent, name) |
| ) { |
| report(node); |
| } |
|
|
| |
| } else if (IMPORT_TYPES.has(parent.type)) { |
| |
| if ( |
| parent.local && |
| parent.local.name === node.name && |
| isInvalid(name) |
| ) { |
| report(node); |
| } |
| } else if (parent.type === "PropertyDefinition") { |
| if (checkClassFields && isInvalid(name)) { |
| report(node); |
| } |
|
|
| |
| } else if (shouldReport(effectiveParent, name)) { |
| report(node); |
| } |
| }, |
|
|
| PrivateIdentifier(node) { |
| const isClassField = node.parent.type === "PropertyDefinition"; |
|
|
| if (isClassField && !checkClassFields) { |
| return; |
| } |
|
|
| if (isInvalid(node.name)) { |
| report(node); |
| } |
| }, |
| }; |
| }, |
| }; |
|
|