diff --git a/docs/rules/id-denylist.md b/docs/rules/id-denylist.md index 040f26e894c..071a34f7940 100644 --- a/docs/rules/id-denylist.md +++ b/docs/rules/id-denylist.md @@ -13,6 +13,8 @@ This rule will catch disallowed identifiers that are: - variable declarations - function declarations - object properties assigned to during object creation +- class fields +- class methods It will not catch disallowed identifiers that are: @@ -49,6 +51,22 @@ element.callback = function() { var itemSet = { data: [...] }; + +class Foo { + data = []; +} + +class Foo { + #data = []; +} + +class Foo { + callback( {); +} + +class Foo { + #callback( {); +} ``` Examples of **correct** code for this rule with sample `"data", "callback"` restricted identifiers: @@ -75,6 +93,22 @@ callback(); // all function calls are ignored foo.callback(); // all function calls are ignored foo.data; // all property names that are not assignments are ignored + +class Foo { + items = []; +} + +class Foo { + #items = []; +} + +class Foo { + method( {); +} + +class Foo { + #method( {); +} ``` ## When Not To Use It diff --git a/docs/rules/id-length.md b/docs/rules/id-length.md index e9f8d9e1eb8..d8b1b45d212 100644 --- a/docs/rules/id-length.md +++ b/docs/rules/id-length.md @@ -30,6 +30,9 @@ var myObj = { a: 1 }; (a) => { a * a }; class x { } class Foo { x() {} } +class Foo { #x() {} } +class Foo { x = 1 } +class Foo { #x = 1 } function foo(...x) { } function foo([x]) { } var [x] = arr; @@ -61,6 +64,9 @@ var myObj = { apple: 1 }; function foo(num = 0) { } class MyClass { } class Foo { method() {} } +class Foo { #method() {} } +class Foo { field = 1 } +class Foo { #field = 1 } function foo(...args) { } function foo([longName]) { } var { prop } = {}; diff --git a/docs/rules/id-match.md b/docs/rules/id-match.md index 0d9deebd76d..5bb2a1d94c8 100644 --- a/docs/rules/id-match.md +++ b/docs/rules/id-match.md @@ -35,9 +35,20 @@ var MY_FAVORITE_COLOR = "#112C85"; function do_something() { // ... } + obj.do_something = function() { // ... }; + +class My_Class {} + +class myClass { + do_something() {} +} + +class myClass { + #do_something() {} +} ``` Examples of **correct** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$"` option: @@ -52,13 +63,26 @@ do_something(); var obj = { my_pref: 1 }; + +class myClass {} + +class myClass { + doSomething() {} +} + +class myClass { + #doSomething() {} +} ``` This rule has an object option: -* `"properties": true` requires object properties to match the specified regular expression +* `"properties": false` (default) does not check object properties +* `"properties": true` requires object literal properties and member expression assignment properties to match the specified regular expression +* `"classFields": false` (default) does not class field names +* `"classFields": true` requires class field names to match the specified regular expression +* `"onlyDeclarations": false` (default) requires all variable names to match the specified regular expression * `"onlyDeclarations": true` requires only `var`, `function`, and `class` declarations to match the specified regular expression -* `"onlyDeclarations": false` requires all variable names to match the specified regular expression * `"ignoreDestructuring": false` (default) enforces `id-match` for destructured identifiers * `"ignoreDestructuring": true` does not check destructured identifiers @@ -74,6 +98,22 @@ var obj = { }; ``` +### classFields + +Examples of **incorrect** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", { "classFields": true }` options: + +```js +/*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$", { "properties": true }]*/ + +class myClass { + my_pref = 1; +} + +class myClass { + #my_pref = 1; +} +``` + ### onlyDeclarations Examples of **correct** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", { "onlyDeclarations": true }` options: diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js index d34656cfabe..5d2babd9fc7 100644 --- a/lib/rules/camelcase.js +++ b/lib/rules/camelcase.js @@ -55,32 +55,25 @@ module.exports = { ], messages: { - notCamelCase: "Identifier '{{name}}' is not in camel case." + notCamelCase: "Identifier '{{name}}' is not in camel case.", + notCamelCasePrivate: "#{{name}} is not in camel case." } }, create(context) { - const options = context.options[0] || {}; - let properties = options.properties || ""; + const properties = options.properties === "never" ? "never" : "always"; const ignoreDestructuring = options.ignoreDestructuring; const ignoreImports = options.ignoreImports; const ignoreGlobals = options.ignoreGlobals; const allow = options.allow || []; - let globalScope; - - if (properties !== "always" && properties !== "never") { - properties = "always"; - } - //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- // contains reported nodes to avoid reporting twice on destructuring with shorthand notation - const reported = []; - const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]); + const reported = new Set(); /** * Checks if a string contains an underscore and isn't all upper-case @@ -89,9 +82,10 @@ module.exports = { * @private */ function isUnderscored(name) { + const nameBody = name.replace(/^_+|_+$/gu, ""); // if there's an underscore, it might be A_CONSTANT, which is okay - return name.includes("_") && name !== name.toUpperCase(); + return nameBody.includes("_") && nameBody !== nameBody.toUpperCase(); } /** @@ -107,94 +101,76 @@ module.exports = { } /** - * Checks if a parent of a node is an ObjectPattern. - * @param {ASTNode} node The node to check. - * @returns {boolean} if the node is inside an ObjectPattern + * Checks if a given name is good or not. + * @param {string} name The name to check. + * @returns {boolean} `true` if the name is good. * @private */ - function isInsideObjectPattern(node) { - let current = node; - - while (current) { - const parent = current.parent; - - if (parent && parent.type === "Property" && parent.computed && parent.key === current) { - return false; - } - - if (current.type === "ObjectPattern") { - return true; - } - - current = parent; - } - - return false; + function isGoodName(name) { + return !isUnderscored(name) || isAllowed(name); } /** - * Checks whether the given node represents assignment target property in destructuring. - * - * For examples: - * ({a: b.foo} = c); // => true for `foo` - * ([a.foo] = b); // => true for `foo` - * ([a.foo = 1] = b); // => true for `foo` - * ({...a.foo} = b); // => true for `foo` - * @param {ASTNode} node An Identifier node to check - * @returns {boolean} True if the node is an assignment target property in destructuring. + * Checks if a given identifier reference or member expression is an assignment + * target. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is an assignment target. */ - function isAssignmentTargetPropertyInDestructuring(node) { - if ( - node.parent.type === "MemberExpression" && - node.parent.property === node && - !node.parent.computed - ) { - const effectiveParent = node.parent.parent; - - return ( - effectiveParent.type === "Property" && - effectiveParent.value === node.parent && - effectiveParent.parent.type === "ObjectPattern" || - effectiveParent.type === "ArrayPattern" || - effectiveParent.type === "RestElement" || - ( - effectiveParent.type === "AssignmentPattern" && - effectiveParent.left === node.parent - ) - ); - } - return false; - } + function isAssignmentTarget(node) { + const parent = node.parent; - /** - * Checks whether the given node represents a reference to a global variable that is not declared in the source code. - * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. - * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a reference to a global variable. - */ - function isReferenceToGlobalVariable(node) { - const variable = globalScope.set.get(node.name); + switch (parent.type) { + case "AssignmentExpression": + case "AssignmentPattern": + return parent.left === node; + + case "Property": + return ( + parent.parent.type === "ObjectPattern" && + parent.value === node + ); + case "ArrayPattern": + case "RestElement": + return true; - return variable && variable.defs.length === 0 && - variable.references.some(ref => ref.identifier === node); + default: + return false; + } } /** - * Checks whether the given node represents a reference to a property of an object in an object literal expression. - * This allows to differentiate between a global variable that is allowed to be used as a reference, and the key - * of the expressed object (which shouldn't be allowed). - * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a property name of an object literal expression + * Checks if a given binding identifier uses the original name as-is. + * - If it's in object destructuring, the original name is its property name. + * - If it's in import declaration, the original name is its exported name. + * @param {ASTNode} node The `Identifier` node to check. + * @returns {boolean} `true` if the identifier uses the original name as-is. */ - function isPropertyNameInObjectLiteral(node) { - const parent = node.parent; - - return ( - parent.type === "Property" && - parent.parent.type === "ObjectExpression" && - !parent.computed && - parent.key === node - ); + function equalsToOriginalName(node) { + const localName = node.name; + const valueNode = node.parent.type === "AssignmentPattern" + ? node.parent + : node; + const parent = valueNode.parent; + + switch (parent.type) { + case "Property": + return ( + parent.parent.type === "ObjectPattern" && + parent.value === valueNode && + !parent.computed && + parent.key.type === "Identifier" && + parent.key.name === localName + ); + + case "ImportSpecifier": + return ( + parent.local === node && + parent.imported.name === localName + ); + + default: + return false; + } } /** @@ -204,122 +180,214 @@ module.exports = { * @private */ function report(node) { - if (!reported.includes(node)) { - reported.push(node); - context.report({ node, messageId: "notCamelCase", data: { name: node.name } }); + if (reported.has(node.range[0])) { + return; } + reported.add(node.range[0]); + + // Report it. + context.report({ + node, + messageId: node.type === "PrivateIdentifier" + ? "notCamelCasePrivate" + : "notCamelCase", + data: { name: node.name } + }); } - return { + /** + * Reports an identifier reference or a binding identifier. + * @param {ASTNode} node The `Identifier` node to report. + * @returns {void} + */ + function reportReferenceId(node) { - Program() { - globalScope = context.getScope(); - }, + /* + * For backward compatibility, if it's in callings then ignore it. + * Not sure why it is. + */ + if ( + node.parent.type === "CallExpression" || + node.parent.type === "NewExpression" + ) { + return; + } - Identifier(node) { + /* + * For backward compatibility, if it's a default value of + * destructuring/parameters then ignore it. + * Not sure why it is. + */ + if ( + node.parent.type === "AssignmentPattern" && + node.parent.right === node + ) { + return; + } - /* - * Leading and trailing underscores are commonly used to flag - * private/protected identifiers, strip them before checking if underscored - */ - const name = node.name, - nameIsUnderscored = isUnderscored(name.replace(/^_+|_+$/gu, "")), - effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; + /* + * The `ignoreDestructuring` flag skips the identifiers that uses + * the property name as-is. + */ + if (ignoreDestructuring && equalsToOriginalName(node)) { + return; + } - // First, we ignore the node if it match the ignore list - if (isAllowed(name)) { - return; - } + report(node); + } + + return { - // Check if it's a global variable - if (ignoreGlobals && isReferenceToGlobalVariable(node) && !isPropertyNameInObjectLiteral(node)) { + // Report camelcase of global variable references ------------------ + Program() { + if (ignoreGlobals) { return; } - // MemberExpressions get special rules - if (node.parent.type === "MemberExpression") { + const scope = context.getScope(); - // "never" check properties - if (properties === "never") { - return; + // Defined globals in config files or directive comments. + for (const variable of scope.variables) { + if ( + variable.identifiers.length > 0 || + isGoodName(variable.name) + ) { + continue; } + for (const reference of variable.references) { - // Always report underscored object names - if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && nameIsUnderscored) { - report(node); + /* + * For backward compatibility, this rule reports read-only + * references as well. + */ + reportReferenceId(reference.identifier); + } + } - // Report AssignmentExpressions only if they are the left side of the assignment - } else if (effectiveParent.type === "AssignmentExpression" && nameIsUnderscored && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) { - report(node); + // Undefined globals. + for (const reference of scope.through) { + const id = reference.identifier; - } else if (isAssignmentTargetPropertyInDestructuring(node) && nameIsUnderscored) { - report(node); + if (isGoodName(id.name)) { + continue; } - /* - * Properties have their own rules, and - * AssignmentPattern nodes can be treated like Properties: - * e.g.: const { no_camelcased = false } = bar; - */ - } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") { - - if (node.parent.parent && node.parent.parent.type === "ObjectPattern") { - if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) { - report(node); - } + /* + * For backward compatibility, this rule reports read-only + * references as well. + */ + reportReferenceId(id); + } + }, - const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name; + // Report camelcase of declared variables -------------------------- + [[ + "VariableDeclaration", + "FunctionDeclaration", + "FunctionExpression", + "ArrowFunctionExpression", + "ClassDeclaration", + "ClassExpression", + "CatchClause" + ]](node) { + for (const variable of context.getDeclaredVariables(node)) { + if (isGoodName(variable.name)) { + continue; + } + const id = variable.identifiers[0]; - if (nameIsUnderscored && node.parent.computed) { - report(node); - } + // Report declaration. + if (!(ignoreDestructuring && equalsToOriginalName(id))) { + report(id); + } - // prevent checking righthand side of destructured object - if (node.parent.key === node && node.parent.value !== node) { - return; + /* + * For backward compatibility, report references as well. + * It looks unnecessary because declarations are reported. + */ + for (const reference of variable.references) { + if (reference.init) { + continue; // Skip the write references of initializers. } + reportReferenceId(reference.identifier); + } + } + }, - const valueIsUnderscored = node.parent.value.name && nameIsUnderscored; + // Report camelcase in properties ---------------------------------- + [[ + "ObjectExpression > Property[computed!=true] > Identifier.key", + "MethodDefinition[computed!=true] > Identifier.key", + "PropertyDefinition[computed!=true] > Identifier.key", + "MethodDefinition > PrivateIdentifier.key", + "PropertyDefinition > PrivateIdentifier.key" + ]](node) { + if (properties === "never" || isGoodName(node.name)) { + return; + } + report(node); + }, + "MemberExpression[computed!=true] > Identifier.property"(node) { + if ( + properties === "never" || + !isAssignmentTarget(node.parent) || // ← ignore read-only references. + isGoodName(node.name) + ) { + return; + } + report(node); + }, - // ignore destructuring if the option is set, unless a new identifier is created - if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) { - report(node); - } + // Report camelcase in import -------------------------------------- + ImportDeclaration(node) { + for (const variable of context.getDeclaredVariables(node)) { + if (isGoodName(variable.name)) { + continue; } + const id = variable.identifiers[0]; - // "never" check properties or always ignore destructuring - if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) { - return; + // Report declaration. + if (!(ignoreImports && equalsToOriginalName(id))) { + report(id); } - // don't check right hand side of AssignmentExpression to prevent duplicate warnings - if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) { - report(node); + /* + * For backward compatibility, report references as well. + * It looks unnecessary because declarations are reported. + */ + for (const reference of variable.references) { + reportReferenceId(reference.identifier); } + } + }, - // Check if it's an import specifier - } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) { - - if (node.parent.type === "ImportSpecifier" && ignoreImports) { - return; - } + // Report camelcase in re-export ----------------------------------- + [[ + "ExportAllDeclaration > Identifier.exported", + "ExportSpecifier > Identifier.exported" + ]](node) { + if (isGoodName(node.name)) { + return; + } + report(node); + }, - // Report only if the local imported identifier is underscored - if ( - node.parent.local && - node.parent.local.name === node.name && - nameIsUnderscored - ) { - report(node); - } + // Report camelcase in labels -------------------------------------- + [[ + "LabeledStatement > Identifier.label", - // Report anything that is underscored that isn't a CallExpression - } else if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) { - report(node); + /* + * For backward compatibility, report references as well. + * It looks unnecessary because declarations are reported. + */ + "BreakStatement > Identifier.label", + "ContinueStatement > Identifier.label" + ]](node) { + if (isGoodName(node.name)) { + return; } + report(node); } - }; - } }; diff --git a/lib/rules/class-methods-use-this.js b/lib/rules/class-methods-use-this.js index 2cc5cc41842..a6e82dfb620 100644 --- a/lib/rules/class-methods-use-this.js +++ b/lib/rules/class-methods-use-this.js @@ -66,7 +66,14 @@ module.exports = { * @private */ function isInstanceMethod(node) { - return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition"; + switch (node.type) { + case "MethodDefinition": + return !node.static && node.kind !== "constructor"; + case "PropertyDefinition": + return !node.static; + default: + return false; + } } /** @@ -94,6 +101,7 @@ module.exports = { if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) { context.report({ node, + loc: astUtils.getFunctionHeadLoc(node, context.getSourceCode()), messageId: "missingThis", data: { name: astUtils.getFunctionNameWithKind(node) @@ -118,6 +126,8 @@ module.exports = { "FunctionDeclaration:exit": exitFunction, FunctionExpression: enterFunction, "FunctionExpression:exit": exitFunction, + "PropertyDefinition > ArrowFunctionExpression.value": enterFunction, + "PropertyDefinition > ArrowFunctionExpression.value:exit": exitFunction, ThisExpression: markThisUsed, Super: markThisUsed }; diff --git a/lib/rules/computed-property-spacing.js b/lib/rules/computed-property-spacing.js index 53fdb8f4e41..9eb665c1794 100644 --- a/lib/rules/computed-property-spacing.js +++ b/lib/rules/computed-property-spacing.js @@ -195,7 +195,8 @@ module.exports = { }; if (enforceForClassMembers) { - listeners.MethodDefinition = checkSpacing("key"); + listeners.MethodDefinition = + listeners.PropertyDefinition = listeners.Property; } return listeners; diff --git a/lib/rules/func-names.js b/lib/rules/func-names.js index ecfedb9e0e9..d6fd9e07199 100644 --- a/lib/rules/func-names.js +++ b/lib/rules/func-names.js @@ -118,6 +118,7 @@ module.exports = { return isObjectOrClassMethod(node) || (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) || (parent.type === "Property" && parent.value === node) || + (parent.type === "PropertyDefinition" && parent.value === node) || (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) || (parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node); } diff --git a/lib/rules/id-blacklist.js b/lib/rules/id-blacklist.js index 4fbba909fde..60f6e73ed05 100644 --- a/lib/rules/id-blacklist.js +++ b/lib/rules/id-blacklist.js @@ -205,7 +205,17 @@ module.exports = { * @private */ function report(node) { - if (!reportedNodes.has(node)) { + + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (!reportedNodes.has(node.range.toString())) { context.report({ node, messageId: "restricted", @@ -213,8 +223,9 @@ module.exports = { name: node.name } }); - reportedNodes.add(node); + reportedNodes.add(node.range.toString()); } + } return { diff --git a/lib/rules/id-denylist.js b/lib/rules/id-denylist.js index 112fd8a9d55..ea9dfddd62e 100644 --- a/lib/rules/id-denylist.js +++ b/lib/rules/id-denylist.js @@ -69,14 +69,14 @@ function isRenamedImport(node) { } /** - * Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring. + * Checks whether the given node is an ObjectPattern destructuring. * * Examples: - * const { a : b } = foo; // node `a` is renamed node. + * const { a : b } = foo; * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring. + * @returns {boolean} `true` if the node is in an ObjectPattern destructuring. */ -function isRenamedInDestructuring(node) { +function isPropertyNameInDestructuring(node) { const parent = node.parent; return ( @@ -84,27 +84,11 @@ function isRenamedInDestructuring(node) { !parent.computed && parent.type === "Property" && parent.parent.type === "ObjectPattern" && - parent.value !== node && parent.key === node ) ); } -/** - * Checks whether the given node represents shorthand definition of a property in an object literal. - * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a shorthand property definition. - */ -function isShorthandPropertyDefinition(node) { - const parent = node.parent; - - return ( - parent.type === "Property" && - parent.parent.type === "ObjectExpression" && - parent.shorthand - ); -} - //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -128,7 +112,8 @@ module.exports = { uniqueItems: true }, messages: { - restricted: "Identifier '{{name}}' is restricted." + restricted: "Identifier '{{name}}' is restricted.", + restrictedPrivate: "Identifier '#{{name}}' is restricted." } }, @@ -187,11 +172,8 @@ module.exports = { parent.type !== "CallExpression" && parent.type !== "NewExpression" && !isRenamedImport(node) && - !isRenamedInDestructuring(node) && - !( - isReferenceToGlobalVariable(node) && - !isShorthandPropertyDefinition(node) - ) + !isPropertyNameInDestructuring(node) && + !isReferenceToGlobalVariable(node) ); } @@ -202,15 +184,27 @@ module.exports = { * @private */ function report(node) { - if (!reportedNodes.has(node)) { + + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (!reportedNodes.has(node.range.toString())) { + const isPrivate = node.type === "PrivateIdentifier"; + context.report({ node, - messageId: "restricted", + messageId: isPrivate ? "restrictedPrivate" : "restricted", data: { name: node.name } }); - reportedNodes.add(node); + reportedNodes.add(node.range.toString()); } } @@ -220,7 +214,10 @@ module.exports = { globalScope = context.getScope(); }, - Identifier(node) { + [[ + "Identifier", + "PrivateIdentifier" + ]](node) { if (isRestricted(node.name) && shouldCheck(node)) { report(node); } diff --git a/lib/rules/id-length.js b/lib/rules/id-length.js index 4df081ff9fe..64c600f8343 100644 --- a/lib/rules/id-length.js +++ b/lib/rules/id-length.js @@ -55,7 +55,9 @@ module.exports = { ], messages: { tooShort: "Identifier name '{{name}}' is too short (< {{min}}).", - tooLong: "Identifier name '{{name}}' is too long (> {{max}})." + tooShortPrivate: "Identifier name '#{{name}}' is too short (< {{min}}).", + tooLong: "Identifier name '{{name}}' is too long (> {{max}}).", + tooLongPrivate: "Identifier name #'{{name}}' is too long (> {{max}})." } }, @@ -66,7 +68,7 @@ module.exports = { const properties = options.properties !== "never"; const exceptions = new Set(options.exceptions); const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u")); - const reportedNode = new Set(); + const reportedNodes = new Set(); /** * Checks if a string matches the provided exception patterns @@ -99,12 +101,14 @@ module.exports = { Property(parent, node) { if (parent.parent.type === "ObjectPattern") { + const isKeyAndValueSame = parent.value.name === parent.key.name; + return ( - parent.value !== parent.key && parent.value === node || - parent.value === parent.key && parent.key === node && properties + !isKeyAndValueSame && parent.value === node || + isKeyAndValueSame && parent.key === node && properties ); } - return properties && !parent.computed && parent.key === node; + return properties && !parent.computed && parent.key.name === node.name; }, ImportDefaultSpecifier: true, RestElement: true, @@ -113,12 +117,16 @@ module.exports = { ClassDeclaration: true, FunctionDeclaration: true, MethodDefinition: true, + PropertyDefinition: true, CatchClause: true, ArrayPattern: true }; return { - Identifier(node) { + [[ + "Identifier", + "PrivateIdentifier" + ]](node) { const name = node.name; const parent = node.parent; @@ -131,11 +139,27 @@ module.exports = { const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type]; - if (isValidExpression && !reportedNode.has(node) && (isValidExpression === true || isValidExpression(parent, node))) { - reportedNode.add(node); + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (isValidExpression && !reportedNodes.has(node.range.toString()) && (isValidExpression === true || isValidExpression(parent, node))) { + reportedNodes.add(node.range.toString()); + + let messageId = isShort ? "tooShort" : "tooLong"; + + if (node.type === "PrivateIdentifier") { + messageId += "Private"; + } + context.report({ node, - messageId: isShort ? "tooShort" : "tooLong", + messageId, data: { name, min: minLength, max: maxLength } }); } diff --git a/lib/rules/id-match.js b/lib/rules/id-match.js index 7e400d037a0..46371a7a661 100644 --- a/lib/rules/id-match.js +++ b/lib/rules/id-match.js @@ -31,6 +31,10 @@ module.exports = { type: "boolean", default: false }, + classFields: { + type: "boolean", + default: false + }, onlyDeclarations: { type: "boolean", default: false @@ -44,7 +48,8 @@ module.exports = { } ], messages: { - notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'." + notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", + notMatchPrivate: "Identifier '#{{name}}' does not match the pattern '{{pattern}}'." } }, @@ -57,7 +62,8 @@ module.exports = { regexp = new RegExp(pattern, "u"); const options = context.options[1] || {}, - properties = !!options.properties, + checkProperties = !!options.properties, + checkClassFields = !!options.classFields, onlyDeclarations = !!options.onlyDeclarations, ignoreDestructuring = !!options.ignoreDestructuring; @@ -66,7 +72,7 @@ module.exports = { //-------------------------------------------------------------------------- // contains reported nodes to avoid reporting twice on destructuring with shorthand notation - const reported = new Map(); + 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"]); @@ -120,16 +126,30 @@ module.exports = { * @private */ function report(node) { - if (!reported.has(node)) { + + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (!reportedNodes.has(node.range.toString())) { + + const messageId = (node.type === "PrivateIdentifier") + ? "notMatchPrivate" : "notMatch"; + context.report({ node, - messageId: "notMatch", + messageId, data: { name: node.name, pattern } }); - reported.set(node, true); + reportedNodes.add(node.range.toString()); } } @@ -142,7 +162,7 @@ module.exports = { if (parent.type === "MemberExpression") { - if (!properties) { + if (!checkProperties) { return; } @@ -176,8 +196,7 @@ module.exports = { } else if (parent.type === "Property" || parent.type === "AssignmentPattern") { if (parent.parent && parent.parent.type === "ObjectPattern") { - if (parent.shorthand && parent.value.left && isInvalid(name)) { - + if (!ignoreDestructuring && parent.shorthand && parent.value.left && isInvalid(name)) { report(node); } @@ -197,7 +216,7 @@ module.exports = { } // never check properties or always ignore destructuring - if (!properties || (ignoreDestructuring && isInsideObjectPattern(node))) { + if (!checkProperties || (ignoreDestructuring && isInsideObjectPattern(node))) { return; } @@ -214,10 +233,29 @@ module.exports = { report(node); } + } else if (parent.type === "PropertyDefinition") { + + if (checkClassFields && isInvalid(name)) { + report(node); + } + // Report anything that is invalid that isn't a CallExpression } 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); + } } }; diff --git a/lib/rules/indent.js b/lib/rules/indent.js index 04f41db9e26..8280d43a654 100644 --- a/lib/rules/indent.js +++ b/lib/rules/indent.js @@ -60,8 +60,10 @@ const KNOWN_NODES = new Set([ "NewExpression", "ObjectExpression", "ObjectPattern", + "PrivateIdentifier", "Program", "Property", + "PropertyDefinition", "RestElement", "ReturnStatement", "SequenceExpression", @@ -1359,6 +1361,45 @@ module.exports = { } }, + PropertyDefinition(node) { + const firstToken = sourceCode.getFirstToken(node); + const maybeSemicolonToken = sourceCode.getLastToken(node); + let keyLastToken = null; + + // Indent key. + if (node.computed) { + const bracketTokenL = sourceCode.getTokenBefore(node.key, astUtils.isOpeningBracketToken); + const bracketTokenR = keyLastToken = sourceCode.getTokenAfter(node.key, astUtils.isClosingBracketToken); + const keyRange = [bracketTokenL.range[1], bracketTokenR.range[0]]; + + if (bracketTokenL !== firstToken) { + offsets.setDesiredOffset(bracketTokenL, firstToken, 0); + } + offsets.setDesiredOffsets(keyRange, bracketTokenL, 1); + offsets.setDesiredOffset(bracketTokenR, bracketTokenL, 0); + } else { + const idToken = keyLastToken = sourceCode.getFirstToken(node.key); + + if (idToken !== firstToken) { + offsets.setDesiredOffset(idToken, firstToken, 1); + } + } + + // Indent initializer. + if (node.value) { + const eqToken = sourceCode.getTokenBefore(node.value, astUtils.isEqToken); + const valueToken = sourceCode.getTokenAfter(eqToken); + + offsets.setDesiredOffset(eqToken, keyLastToken, 1); + offsets.setDesiredOffset(valueToken, eqToken, 1); + if (astUtils.isSemicolonToken(maybeSemicolonToken)) { + offsets.setDesiredOffset(maybeSemicolonToken, eqToken, 1); + } + } else if (astUtils.isSemicolonToken(maybeSemicolonToken)) { + offsets.setDesiredOffset(maybeSemicolonToken, keyLastToken, 1); + } + }, + SwitchStatement(node) { const openingCurly = sourceCode.getTokenAfter(node.discriminant, astUtils.isOpeningBraceToken); const closingCurly = sourceCode.getLastToken(node); diff --git a/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js index 913cf4682f9..14a46647458 100644 --- a/lib/rules/keyword-spacing.js +++ b/lib/rules/keyword-spacing.js @@ -22,7 +22,7 @@ const PREV_TOKEN_M = /^[)\]}>*]$/u; const NEXT_TOKEN_M = /^[{*]$/u; const TEMPLATE_OPEN_PAREN = /\$\{$/u; const TEMPLATE_CLOSE_PAREN = /^\}/u; -const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/u; +const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template|PrivateIdentifier)$/u; const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]); // check duplications. @@ -567,6 +567,7 @@ module.exports = { // Others ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier, MethodDefinition: checkSpacingForProperty, + PropertyDefinition: checkSpacingForProperty, Property: checkSpacingForProperty }; } diff --git a/lib/rules/no-dupe-class-members.js b/lib/rules/no-dupe-class-members.js index b12939d57bc..31d062370ad 100644 --- a/lib/rules/no-dupe-class-members.js +++ b/lib/rules/no-dupe-class-members.js @@ -73,20 +73,21 @@ module.exports = { }, // Reports the node if its name has been declared already. - MethodDefinition(node) { + "MethodDefinition, PropertyDefinition"(node) { const name = astUtils.getStaticPropertyName(node); + const kind = node.type === "MethodDefinition" ? node.kind : "field"; - if (name === null || node.kind === "constructor") { + if (name === null || kind === "constructor") { return; } const state = getState(name, node.static); let isDuplicate = false; - if (node.kind === "get") { + if (kind === "get") { isDuplicate = (state.init || state.get); state.get = true; - } else if (node.kind === "set") { + } else if (kind === "set") { isDuplicate = (state.init || state.set); state.set = true; } else { diff --git a/lib/rules/no-eval.js b/lib/rules/no-eval.js index a020fdee014..4360ac4110e 100644 --- a/lib/rules/no-eval.js +++ b/lib/rules/no-eval.js @@ -247,6 +247,8 @@ module.exports = { "FunctionExpression:exit": exitVarScope, ArrowFunctionExpression: enterVarScope, "ArrowFunctionExpression:exit": exitVarScope, + "PropertyDefinition > *.value": enterVarScope, + "PropertyDefinition > *.value:exit": exitVarScope, ThisExpression(node) { if (!isMember(node.parent, "eval")) { diff --git a/lib/rules/no-extra-semi.js b/lib/rules/no-extra-semi.js index e0a8df0565a..42c24427aa5 100644 --- a/lib/rules/no-extra-semi.js +++ b/lib/rules/no-extra-semi.js @@ -117,7 +117,7 @@ module.exports = { * @param {Node} node A MethodDefinition node of the start point. * @returns {void} */ - MethodDefinition(node) { + "MethodDefinition, PropertyDefinition"(node) { checkForPartOfClassBody(sourceCode.getTokenAfter(node)); } }; diff --git a/lib/rules/no-invalid-this.js b/lib/rules/no-invalid-this.js index a79c586d719..5bb5ea1e0b9 100644 --- a/lib/rules/no-invalid-this.js +++ b/lib/rules/no-invalid-this.js @@ -129,6 +129,10 @@ module.exports = { FunctionExpression: enterFunction, "FunctionExpression:exit": exitFunction, + // Field initializers are implicit functions. + "PropertyDefinition > *.value": enterFunction, + "PropertyDefinition > *.value:exit": exitFunction, + // Reports if `this` of the current context is invalid. ThisExpression(node) { const current = stack.getCurrent(); diff --git a/lib/rules/no-multi-assign.js b/lib/rules/no-multi-assign.js index d2606a1502a..d517a6beb95 100644 --- a/lib/rules/no-multi-assign.js +++ b/lib/rules/no-multi-assign.js @@ -45,16 +45,21 @@ module.exports = { const options = context.options[0] || { ignoreNonDeclaration: false }; - const targetParent = options.ignoreNonDeclaration ? ["VariableDeclarator"] : ["AssignmentExpression", "VariableDeclarator"]; + const selectors = [ + "VariableDeclarator > AssignmentExpression.init", + "PropertyDefinition > AssignmentExpression.value" + ]; + + if (!options.ignoreNonDeclaration) { + selectors.push("AssignmentExpression > AssignmentExpression.right"); + } return { - AssignmentExpression(node) { - if (targetParent.indexOf(node.parent.type) !== -1) { - context.report({ - node, - messageId: "unexpectedChain" - }); - } + [selectors](node) { + context.report({ + node, + messageId: "unexpectedChain" + }); } }; diff --git a/lib/rules/no-setter-return.js b/lib/rules/no-setter-return.js index 9c79240dda1..90c2095ac20 100644 --- a/lib/rules/no-setter-return.js +++ b/lib/rules/no-setter-return.js @@ -93,6 +93,7 @@ function isSetter(node, scope) { const parent = node.parent; if ( + (parent.type === "Property" || parent.type === "MethodDefinition") && parent.kind === "set" && parent.value === node ) { diff --git a/lib/rules/no-undef-init.js b/lib/rules/no-undef-init.js index 5c240fef742..e23adf34cca 100644 --- a/lib/rules/no-undef-init.js +++ b/lib/rules/no-undef-init.js @@ -34,37 +34,88 @@ module.exports = { const sourceCode = context.getSourceCode(); + /** + * Get the node of init target. + * @param {ASTNode} node The node to get. + * @returns {ASTNode} The node of init target. + */ + function getIdNode(node) { + switch (node.type) { + case "VariableDeclarator": + return node.id; + case "PropertyDefinition": + return node.key; + default: + throw new Error("unreachable"); + } + } + + /** + * Get the node of init value. + * @param {ASTNode} node The node to get. + * @returns {ASTNode} The node of init value. + */ + function getInitNode(node) { + switch (node.type) { + case "VariableDeclarator": + return node.init; + case "PropertyDefinition": + return node.value; + default: + throw new Error("unreachable"); + } + } + + /** + * Get the parent kind of the node. + * @param {ASTNode} node The node to get. + * @returns {string} The parent kind. + */ + function getParentKind(node) { + switch (node.type) { + case "VariableDeclarator": + return node.parent.kind; + case "PropertyDefinition": + return "field"; + default: + throw new Error("unreachable"); + } + } + return { - VariableDeclarator(node) { - const name = sourceCode.getText(node.id), - init = node.init && node.init.name, + "VariableDeclarator, PropertyDefinition"(node) { + const idNode = getIdNode(node), + name = sourceCode.getText(idNode), + initNode = getInitNode(node), + initIsUndefined = initNode && initNode.type === "Identifier" && initNode.name === "undefined", + parentKind = getParentKind(node), scope = context.getScope(), undefinedVar = astUtils.getVariableByName(scope, "undefined"), shadowed = undefinedVar && undefinedVar.defs.length > 0, - lastToken = sourceCode.getLastToken(node); + lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken); - if (init === "undefined" && node.parent.kind !== "const" && !shadowed) { + if (initIsUndefined && parentKind !== "const" && !shadowed) { context.report({ node, messageId: "unnecessaryUndefinedInit", data: { name }, fix(fixer) { - if (node.parent.kind === "var") { + if (parentKind === "var") { return null; } - if (node.id.type === "ArrayPattern" || node.id.type === "ObjectPattern") { + if (idNode.type === "ArrayPattern" || idNode.type === "ObjectPattern") { // Don't fix destructuring assignment to `undefined`. return null; } - if (sourceCode.commentsExistBetween(node.id, lastToken)) { + if (sourceCode.commentsExistBetween(idNode, lastToken)) { return null; } - return fixer.removeRange([node.id.range[1], node.range[1]]); + return fixer.removeRange([idNode.range[1], lastToken.range[1]]); } }); } diff --git a/lib/rules/no-underscore-dangle.js b/lib/rules/no-underscore-dangle.js index 87d2336fa4a..6dc0d46f218 100644 --- a/lib/rules/no-underscore-dangle.js +++ b/lib/rules/no-underscore-dangle.js @@ -253,7 +253,9 @@ module.exports = { node, messageId: "unexpectedUnderscore", data: { - identifier + identifier: node.key.type === "PrivateIdentifier" + ? `#${identifier}` + : identifier } }); } @@ -268,6 +270,7 @@ module.exports = { VariableDeclarator: checkForDanglingUnderscoreInVariableExpression, MemberExpression: checkForDanglingUnderscoreInMemberExpression, MethodDefinition: checkForDanglingUnderscoreInMethod, + PropertyDefinition: checkForDanglingUnderscoreInMethod, Property: checkForDanglingUnderscoreInMethod, FunctionExpression: checkForDanglingUnderscoreInFunction, ArrowFunctionExpression: checkForDanglingUnderscoreInFunction diff --git a/lib/rules/no-unreachable.js b/lib/rules/no-unreachable.js index 415631a6f7d..5a2c52ad54d 100644 --- a/lib/rules/no-unreachable.js +++ b/lib/rules/no-unreachable.js @@ -8,6 +8,13 @@ // Helpers //------------------------------------------------------------------------------ +/** + * @typedef {Object} ClassInfo + * @property {ClassInfo | null} upper The class info that encloses this class. + * @property {boolean} hasConstructor The flag about having user-defined constructor. + * @property {boolean} hasSuperCall The flag about having `super()` expressions. + */ + /** * Checks whether or not a given variable declarator has the initializer. * @param {ASTNode} node A VariableDeclarator node to check. @@ -120,6 +127,10 @@ module.exports = { create(context) { let currentCodePath = null; + /** @type {ClassInfo | null} */ + let classInfo = null; + + /** @type {ConsecutiveRange} */ const range = new ConsecutiveRange(context.getSourceCode()); /** @@ -130,7 +141,7 @@ module.exports = { function reportIfUnreachable(node) { let nextNode = null; - if (node && currentCodePath.currentSegments.every(isUnreachable)) { + if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) { // Store this statement to distinguish consecutive statements. if (range.isEmpty) { @@ -212,6 +223,39 @@ module.exports = { "Program:exit"() { reportIfUnreachable(); + }, + + // Address class fields. + "ClassDeclaration, ClassExpression"() { + classInfo = { + upper: classInfo, + hasConstructor: false, + hasSuperCall: false + }; + }, + "ClassDeclaration, ClassExpression:exit"(node) { + const { hasConstructor, hasSuperCall } = classInfo; + const hasExtends = Boolean(node.superClass); + + classInfo = classInfo.upper; + + if (hasConstructor && hasExtends && !hasSuperCall) { + for (const element of node.body.body) { + if (element.type === "PropertyDefinition") { + reportIfUnreachable(element); + } + } + } + }, + "MethodDefinition[kind='constructor']"() { + if (classInfo) { + classInfo.hasConstructor = true; + } + }, + "CallExpression > Super.callee"() { + if (classInfo) { + classInfo.hasSuperCall = true; + } } }; } diff --git a/lib/rules/no-useless-computed-key.js b/lib/rules/no-useless-computed-key.js index a1cacc29612..08df712657c 100644 --- a/lib/rules/no-useless-computed-key.js +++ b/lib/rules/no-useless-computed-key.js @@ -103,7 +103,8 @@ module.exports = { return { Property: check, - MethodDefinition: enforceForClassMembers ? check : noop + MethodDefinition: enforceForClassMembers ? check : noop, + PropertyDefinition: enforceForClassMembers ? check : noop }; } }; diff --git a/lib/rules/operator-linebreak.js b/lib/rules/operator-linebreak.js index 18da5c55b4a..40ca880572f 100644 --- a/lib/rules/operator-linebreak.js +++ b/lib/rules/operator-linebreak.js @@ -238,6 +238,11 @@ module.exports = { validateNode(node, node.id); } }, + PropertyDefinition(node) { + if (node.value) { + validateNode(node, node.key); + } + }, ConditionalExpression(node) { validateNode(node, node.test); validateNode(node, node.consequent); diff --git a/lib/rules/quotes.js b/lib/rules/quotes.js index da7e127493e..54946ff7814 100644 --- a/lib/rules/quotes.js +++ b/lib/rules/quotes.js @@ -216,6 +216,7 @@ module.exports = { // LiteralPropertyName. case "Property": + case "PropertyDefinition": case "MethodDefinition": return parent.key === node && !parent.computed; diff --git a/lib/rules/semi-spacing.js b/lib/rules/semi-spacing.js index 5c546f29028..e9f2ceb5c8c 100644 --- a/lib/rules/semi-spacing.js +++ b/lib/rules/semi-spacing.js @@ -238,7 +238,8 @@ module.exports = { if (node.test) { checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node); } - } + }, + PropertyDefinition: checkNode }; } }; diff --git a/lib/rules/semi-style.js b/lib/rules/semi-style.js index 0c9bec4c85e..186f5c0d6ec 100644 --- a/lib/rules/semi-style.js +++ b/lib/rules/semi-style.js @@ -15,15 +15,13 @@ const astUtils = require("./utils/ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -const SELECTOR = `:matches(${ - [ - "BreakStatement", "ContinueStatement", "DebuggerStatement", - "DoWhileStatement", "ExportAllDeclaration", - "ExportDefaultDeclaration", "ExportNamedDeclaration", - "ExpressionStatement", "ImportDeclaration", "ReturnStatement", - "ThrowStatement", "VariableDeclaration" - ].join(",") -})`; +const SELECTOR = [ + "BreakStatement", "ContinueStatement", "DebuggerStatement", + "DoWhileStatement", "ExportAllDeclaration", + "ExportDefaultDeclaration", "ExportNamedDeclaration", + "ExpressionStatement", "ImportDeclaration", "ReturnStatement", + "ThrowStatement", "VariableDeclaration", "PropertyDefinition" +].join(","); /** * Get the child node list of a given node. @@ -35,7 +33,7 @@ const SELECTOR = `:matches(${ function getChildren(node) { const t = node.type; - if (t === "BlockStatement" || t === "Program") { + if (t === "BlockStatement" || t === "Program" || t === "ClassBody") { return node.body; } if (t === "SwitchCase") { diff --git a/lib/rules/semi.js b/lib/rules/semi.js index d2f0670427b..87086e981b0 100644 --- a/lib/rules/semi.js +++ b/lib/rules/semi.js @@ -204,6 +204,9 @@ module.exports = { if (isEndOfArrowBlock(sourceCode.getLastToken(node, 1))) { return false; } + if (t === "PropertyDefinition") { + return Boolean(t.value); + } return true; } @@ -329,7 +332,8 @@ module.exports = { if (!/(?:Class|Function)Declaration/u.test(node.declaration.type)) { checkForSemicolon(node); } - } + }, + PropertyDefinition: checkForSemicolon }; } diff --git a/lib/rules/space-infix-ops.js b/lib/rules/space-infix-ops.js index 3c550984fc6..b0511e13cba 100644 --- a/lib/rules/space-infix-ops.js +++ b/lib/rules/space-infix-ops.js @@ -4,6 +4,8 @@ */ "use strict"; +const { isEqToken } = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -164,7 +166,29 @@ module.exports = { BinaryExpression: checkBinary, LogicalExpression: checkBinary, ConditionalExpression: checkConditional, - VariableDeclarator: checkVar + VariableDeclarator: checkVar, + + PropertyDefinition(node) { + if (!node.value) { + return; + } + + /* + * Because of computed properties and type annotations, some + * tokens may exist between `node.key` and `=`. + * Therefore, find the `=` from the right. + */ + const operatorToken = sourceCode.getTokenBefore(node.value, isEqToken); + const leftToken = sourceCode.getTokenBefore(operatorToken); + const rightToken = sourceCode.getTokenAfter(operatorToken); + + if ( + !sourceCode.isSpaceBetweenTokens(leftToken, operatorToken) || + !sourceCode.isSpaceBetweenTokens(operatorToken, rightToken) + ) { + report(node, operatorToken); + } + } }; } diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index 6b853001132..478f6c58157 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -266,6 +266,7 @@ function getStaticPropertyName(node) { return getStaticPropertyName(node.expression); case "Property": + case "PropertyDefinition": case "MethodDefinition": prop = node.key; break; @@ -407,6 +408,7 @@ function isSameReference(left, right, disableStaticComputedKey = false) { return true; case "Identifier": + case "PrivateIdentifier": return left.name === right.name; case "Literal": return equalLiteralValue(left, right); @@ -516,6 +518,15 @@ function isParenthesised(sourceCode, node) { nextToken.value === ")" && nextToken.range[0] >= node.range[1]; } +/** + * Checks if the given token is a `=` token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a `=` token. + */ +function isEqToken(token) { + return token.value === "=" && token.type === "Punctuator"; +} + /** * Checks if the given token is an arrow token or not. * @param {Token} token The token to check. @@ -649,6 +660,16 @@ function isKeywordToken(token) { * @returns {Token} `(` token. */ function getOpeningParenOfParams(node, sourceCode) { + + // If the node is an arrow function and doesn't have parens, this returns the identifier of the first param. + if (node.type === "ArrowFunctionExpression" && node.params.length === 1) { + const argToken = sourceCode.getFirstToken(node.params[0]); + const maybeParenToken = sourceCode.getTokenBefore(argToken); + + return isOpeningParenToken(maybeParenToken) ? maybeParenToken : argToken; + } + + // Otherwise, returns paren. return node.id ? sourceCode.getTokenAfter(node.id, isOpeningParenToken) : sourceCode.getFirstToken(node, isOpeningParenToken); @@ -794,6 +815,7 @@ module.exports = { isOpeningBracketToken, isOpeningParenToken, isSemicolonToken, + isEqToken, /** * Checks whether or not a given node is a string literal. @@ -923,6 +945,16 @@ module.exports = { * @returns {boolean} The function node is the default `this` binding. */ isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) { + + /* + * Class field initializers are implicit functions, but ESTree doesn't have the AST node of field initializers. + * Therefore, A expression node at `PropertyDefinition#value` is a function. + * In this case, `this` is always not default binding. + */ + if (node && node.parent && node.parent.type === "PropertyDefinition" && node.value === node) { + return false; + } + if ( (capIsConstructor && isES5Constructor(node)) || hasJSDocThisTag(node, sourceCode) @@ -983,8 +1015,10 @@ module.exports = { * class A { get foo() { ... } } * class A { set foo() { ... } } * class A { static foo() { ... } } + * class A { foo = function() { ... } } */ case "Property": + case "PropertyDefinition": case "MethodDefinition": return parent.value !== currentNode; @@ -1323,6 +1357,16 @@ module.exports = { * - `class A { static async foo() {} }` .... `static async method 'foo'` * - `class A { static get foo() {} }` ...... `static getter 'foo'` * - `class A { static set foo(a) {} }` ..... `static setter 'foo'` + * - `class A { foo = () => {}; }` .......... `method 'foo'` + * - `class A { foo = function() {}; }` ..... `method 'foo'` + * - `class A { foo = function bar() {}; }` . `method 'foo'` + * - `class A { static foo = () => {}; }` ... `static method 'foo'` + * - `class A { '#foo' = () => {}; }` ....... `method '#foo'` + * - `class A { #foo = () => {}; }` ......... `private method #foo` + * - `class A { static #foo = () => {}; }` .. `static private method #foo` + * - `class A { '#foo'() {} }` .............. `method '#foo'` + * - `class A { #foo() {} }` ................ `private method #foo` + * - `class A { static #foo() {} }` ......... `static private method #foo` * @param {ASTNode} node The function node to get. * @returns {string} The name and kind of the function node. */ @@ -1330,8 +1374,15 @@ module.exports = { const parent = node.parent; const tokens = []; - if (parent.type === "MethodDefinition" && parent.static) { - tokens.push("static"); + if (parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") { + + // The proposal uses `static` word consistently before visibility words: https://github.com/tc39/proposal-static-class-features + if (parent.static) { + tokens.push("static"); + } + if (!parent.computed && parent.key.type === "PrivateIdentifier") { + tokens.push("private"); + } } if (node.async) { tokens.push("async"); @@ -1340,9 +1391,7 @@ module.exports = { tokens.push("generator"); } - if (node.type === "ArrowFunctionExpression") { - tokens.push("arrow", "function"); - } else if (parent.type === "Property" || parent.type === "MethodDefinition") { + if (parent.type === "Property" || parent.type === "MethodDefinition") { if (parent.kind === "constructor") { return "constructor"; } @@ -1353,18 +1402,29 @@ module.exports = { } else { tokens.push("method"); } + } else if (parent.type === "PropertyDefinition") { + tokens.push("method"); } else { + if (node.type === "ArrowFunctionExpression") { + tokens.push("arrow"); + } tokens.push("function"); } - if (node.id) { - tokens.push(`'${node.id.name}'`); - } else { - const name = getStaticPropertyName(parent); + if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") { + if (!parent.computed && parent.key.type === "PrivateIdentifier") { + tokens.push(`#${parent.key.name}`); + } else { + const name = getStaticPropertyName(parent); - if (name !== null) { - tokens.push(`'${name}'`); + if (name !== null) { + tokens.push(`'${name}'`); + } else if (node.id) { + tokens.push(`'${node.id.name}'`); + } } + } else if (node.id) { + tokens.push(`'${node.id.name}'`); } return tokens.join(" "); @@ -1457,6 +1517,12 @@ module.exports = { * ^^^^^^^^^^^^^^ * - `class A { static set foo(a) {} }` * ^^^^^^^^^^^^^^ + * - `class A { foo = function() {} }` + * ^^^^^^^^^^^^^^ + * - `class A { static foo = function() {} }` + * ^^^^^^^^^^^^^^^^^^^^^ + * - `class A { foo = (a, b) => {} }` + * ^^^^^^ * @param {ASTNode} node The function node to get. * @param {SourceCode} sourceCode The source code object to get tokens. * @returns {string} The location of the function node for reporting. @@ -1466,14 +1532,14 @@ module.exports = { let start = null; let end = null; - if (node.type === "ArrowFunctionExpression") { + if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") { + start = parent.loc.start; + end = getOpeningParenOfParams(node, sourceCode).loc.start; + } else if (node.type === "ArrowFunctionExpression") { const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken); start = arrowToken.loc.start; end = arrowToken.loc.end; - } else if (parent.type === "Property" || parent.type === "MethodDefinition") { - start = parent.loc.start; - end = getOpeningParenOfParams(node, sourceCode).loc.start; } else { start = node.loc.start; end = getOpeningParenOfParams(node, sourceCode).loc.start; diff --git a/package.json b/package.json index 8ef283a07e6..1981419c5c1 100644 --- a/package.json +++ b/package.json @@ -52,10 +52,10 @@ "doctrine": "^3.0.0", "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", + "eslint-scope": "^6.0.0", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^2.1.0", + "espree": "^8.0.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", diff --git a/tests/fixtures/parsers/enhanced-parser3.js b/tests/fixtures/parsers/enhanced-parser3.js index bc08e3da47e..fb57e804738 100644 --- a/tests/fixtures/parsers/enhanced-parser3.js +++ b/tests/fixtures/parsers/enhanced-parser3.js @@ -1,8 +1,7 @@ "use strict"; const assert = require("assert"); -const ScopeManager = require("eslint-scope/lib/scope-manager"); -const Referencer = require("eslint-scope/lib/referencer"); +const { ScopeManager, Referencer } = require("eslint-scope"); const vk = require("eslint-visitor-keys"); class EnhancedReferencer extends Referencer { diff --git a/tests/lib/rules/accessor-pairs.js b/tests/lib/rules/accessor-pairs.js index c6341326647..d74913f4e74 100644 --- a/tests/lib/rules/accessor-pairs.js +++ b/tests/lib/rules/accessor-pairs.js @@ -335,6 +335,11 @@ ruleTester.run("accessor-pairs", rule, { options: [{ enforceForClassMembers: true }], parserOptions: { ecmaVersion: 6 } }, + { + code: "class A { get #a() {} }", + options: [{ enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 } + }, // Explicitly disabled option { @@ -1207,6 +1212,26 @@ ruleTester.run("accessor-pairs", rule, { parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition" }] }, + { + code: "class A { set '#a'(foo) {} }", + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Getter is not present for class setter '#a'.", type: "MethodDefinition" }] + }, + { + code: "class A { set #a(foo) {} }", + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Getter is not present for class private setter #a.", type: "MethodDefinition" }] + }, + { + code: "class A { static set '#a'(foo) {} }", + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Getter is not present for class static setter '#a'.", type: "MethodDefinition" }] + }, + { + code: "class A { static set #a(foo) {} }", + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Getter is not present for class static private setter #a.", type: "MethodDefinition" }] + }, // Test that the accessor kind options do not affect each other { @@ -1239,6 +1264,30 @@ ruleTester.run("accessor-pairs", rule, { parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Setter is not present for class getter 'a'.", type: "MethodDefinition" }] }, + { + code: "class A { get '#a'() {} };", + options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Setter is not present for class getter '#a'.", type: "MethodDefinition" }] + }, + { + code: "class A { get #a() {} };", + options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Setter is not present for class private getter #a.", type: "MethodDefinition" }] + }, + { + code: "class A { static get '#a'() {} };", + options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Setter is not present for class static getter '#a'.", type: "MethodDefinition" }] + }, + { + code: "class A { static get #a() {} };", + options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Setter is not present for class static private getter #a.", type: "MethodDefinition" }] + }, // Various kinds of keys { @@ -1424,6 +1473,24 @@ ruleTester.run("accessor-pairs", rule, { { message: "Getter is not present for class setter.", type: "MethodDefinition", column: 28 } ] }, + { + code: "class A { get #a() {} set '#a'(foo) {} }", + options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [ + { message: "Setter is not present for class private getter #a.", type: "MethodDefinition", column: 11 }, + { message: "Getter is not present for class setter '#a'.", type: "MethodDefinition", column: 23 } + ] + }, + { + code: "class A { get '#a'() {} set #a(foo) {} }", + options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [ + { message: "Setter is not present for class getter '#a'.", type: "MethodDefinition", column: 11 }, + { message: "Getter is not present for class private setter #a.", type: "MethodDefinition", column: 25 } + ] + }, // Prototype and static accessors with same keys { diff --git a/tests/lib/rules/camelcase.js b/tests/lib/rules/camelcase.js index 4f9cdca78fe..40b8d0278c9 100644 --- a/tests/lib/rules/camelcase.js +++ b/tests/lib/rules/camelcase.js @@ -376,6 +376,16 @@ ruleTester.run("camelcase", rule, { code: "([obj.foo = obj.fo_o] = bar);", options: [{ properties: "always" }], parserOptions: { ecmaVersion: 6 } + }, + { + code: "class C { camelCase; #camelCase; #camelCase2() {} }", + options: [{ properties: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { snake_case; #snake_case; #snake_case2() {} }", + options: [{ properties: "never" }], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -1320,6 +1330,26 @@ ruleTester.run("camelcase", rule, { options: [{ properties: "always" }], parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "notCamelCase", data: { name: "non_camelcase" } }] + }, + + // class public/private fields, private methods. + { + code: "class C { snake_case; }", + options: [{ properties: "always" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "notCamelCase", data: { name: "snake_case" } }] + }, + { + code: "class C { #snake_case; foo() { this.#snake_case; } }", + options: [{ properties: "always" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "notCamelCasePrivate", data: { name: "snake_case" }, column: 11 }] + }, + { + code: "class C { #snake_case() {} }", + options: [{ properties: "always" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "notCamelCasePrivate", data: { name: "snake_case" } }] } ] }); diff --git a/tests/lib/rules/class-methods-use-this.js b/tests/lib/rules/class-methods-use-this.js index e307d6a0c29..5cc04351c99 100644 --- a/tests/lib/rules/class-methods-use-this.js +++ b/tests/lib/rules/class-methods-use-this.js @@ -30,56 +30,61 @@ ruleTester.run("class-methods-use-this", rule, { { code: "({ a(){} });", parserOptions: { ecmaVersion: 6 } }, { code: "class A { foo() { () => this; } }", parserOptions: { ecmaVersion: 6 } }, { code: "({ a: function () {} });", parserOptions: { ecmaVersion: 6 } }, - { code: "class A { foo() {this} bar() {} }", options: [{ exceptMethods: ["bar"] }], parserOptions: { ecmaVersion: 6 } } + { code: "class A { foo() {this} bar() {} }", options: [{ exceptMethods: ["bar"] }], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { foo = function() {this} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { foo = () => {this} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { foo = () => {super.toString} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { static foo = function() {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { static foo = () => {} }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { code: "class A { foo() {} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() {/**this**/} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() {var a = function () {this};} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() {var a = function () {var b = function(){this}};} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() {window.this} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() {that.this = 'this';} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() { () => undefined; } }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { @@ -87,7 +92,7 @@ ruleTester.run("class-methods-use-this", rule, { options: [{ exceptMethods: ["bar"] }], parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { @@ -95,7 +100,7 @@ ruleTester.run("class-methods-use-this", rule, { options: [{ exceptMethods: ["foo"] }], parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 34, messageId: "missingThis", data: { name: "method 'hasOwnProperty'" } } + { type: "FunctionExpression", line: 1, column: 20, messageId: "missingThis", data: { name: "method 'hasOwnProperty'" } } ] }, { @@ -103,22 +108,71 @@ ruleTester.run("class-methods-use-this", rule, { options: [{ exceptMethods: ["foo"] }], parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 16, messageId: "missingThis", data: { name: "method" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method" } } ] }, { code: "class A { foo(){} 'bar'(){} 123(){} [`baz`](){} [a](){} [f(a)](){} get quux(){} set[a](b){} *quuux(){} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { messageId: "missingThis", data: { name: "method 'foo'" }, type: "FunctionExpression", column: 14 }, - { messageId: "missingThis", data: { name: "method 'bar'" }, type: "FunctionExpression", column: 24 }, - { messageId: "missingThis", data: { name: "method '123'" }, type: "FunctionExpression", column: 32 }, - { messageId: "missingThis", data: { name: "method 'baz'" }, type: "FunctionExpression", column: 44 }, - { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 52 }, - { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 63 }, - { messageId: "missingThis", data: { name: "getter 'quux'" }, type: "FunctionExpression", column: 76 }, - { messageId: "missingThis", data: { name: "setter" }, type: "FunctionExpression", column: 87 }, - { messageId: "missingThis", data: { name: "generator method 'quuux'" }, type: "FunctionExpression", column: 99 } + { messageId: "missingThis", data: { name: "method 'foo'" }, type: "FunctionExpression", column: 11 }, + { messageId: "missingThis", data: { name: "method 'bar'" }, type: "FunctionExpression", column: 19 }, + { messageId: "missingThis", data: { name: "method '123'" }, type: "FunctionExpression", column: 29 }, + { messageId: "missingThis", data: { name: "method 'baz'" }, type: "FunctionExpression", column: 37 }, + { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 49 }, + { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 57 }, + { messageId: "missingThis", data: { name: "getter 'quux'" }, type: "FunctionExpression", column: 68 }, + { messageId: "missingThis", data: { name: "setter" }, type: "FunctionExpression", column: 81 }, + { messageId: "missingThis", data: { name: "generator method 'quuux'" }, type: "FunctionExpression", column: 93 } + ] + }, + { + code: "class A { foo = function() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 25 } + ] + }, + { + code: "class A { foo = () => {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 17 } + ] + }, + { + code: "class A { #foo = function() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 26 } + ] + }, + { + code: "class A { #foo = () => {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 18 } + ] + }, + { + code: "class A { #foo() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 15 } + ] + }, + { + code: "class A { get #foo() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "private getter #foo" }, column: 11, endColumn: 19 } + ] + }, + { + code: "class A { set #foo(x) {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "private setter #foo" }, column: 11, endColumn: 19 } ] } ] diff --git a/tests/lib/rules/complexity.js b/tests/lib/rules/complexity.js index cf7605baffa..9a30b6edbb0 100644 --- a/tests/lib/rules/complexity.js +++ b/tests/lib/rules/complexity.js @@ -118,7 +118,7 @@ ruleTester.run("complexity", rule, { { code: "function a(x) {(function() {while(true){'foo';}})(); (function() {while(true){'bar';}})();}", options: [1], errors: 2 }, { code: "function a(x) {(function() {while(true){'foo';}})(); (function() {'bar';})();}", options: [1], errors: 1 }, { code: "var obj = { a(x) { return x ? 0 : 1; } };", options: [1], parserOptions: { ecmaVersion: 6 }, errors: [makeError("Method 'a'", 2, 1)] }, - { code: "var obj = { a: function b(x) { return x ? 0 : 1; } };", options: [1], errors: [makeError("Method 'b'", 2, 1)] }, + { code: "var obj = { a: function b(x) { return x ? 0 : 1; } };", options: [1], errors: [makeError("Method 'a'", 2, 1)] }, { code: createComplexity(21), errors: [makeError("Function 'test'", 21, 20)] diff --git a/tests/lib/rules/computed-property-spacing.js b/tests/lib/rules/computed-property-spacing.js index a1e5834243e..eff0c0a73bd 100644 --- a/tests/lib/rules/computed-property-spacing.js +++ b/tests/lib/rules/computed-property-spacing.js @@ -99,6 +99,16 @@ ruleTester.run("computed-property-spacing", rule, { options: ["always", { enforceForClassMembers: false }], parserOptions: { ecmaVersion: 6 } }, + { + code: "class A { [ a ]; }", + options: ["never", { enforceForClassMembers: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class A { [a]; }", + options: ["always", { enforceForClassMembers: false }], + parserOptions: { ecmaVersion: 2022 } + }, // valid spacing { @@ -141,6 +151,16 @@ ruleTester.run("computed-property-spacing", rule, { options: ["always", { enforceForClassMembers: true }], parserOptions: { ecmaVersion: 6 } }, + { + code: "A = class { [a]; static [a]; [a] = 0; static [a] = 0; }", + options: ["never", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "A = class { [ a ]; static [ a ]; [ a ] = 0; static [ a ] = 0; }", + options: ["always", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 } + }, // non-computed { @@ -153,6 +173,16 @@ ruleTester.run("computed-property-spacing", rule, { options: ["always", { enforceForClassMembers: true }], parserOptions: { ecmaVersion: 6 } }, + { + code: "A = class { foo; #a; static #b; #c = 0; static #d = 0; }", + options: ["never", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "A = class { foo; #a; static #b; #c = 0; static #d = 0; }", + options: ["always", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 } + }, // handling of parens and comments { @@ -1349,6 +1379,62 @@ ruleTester.run("computed-property-spacing", rule, { } ] }, + { + code: "class A { [ a]; [b ]; [ c ]; [ a] = 0; [b ] = 0; [ c ] = 0; }", + output: "class A { [a]; [b]; [c]; [a] = 0; [b] = 0; [c] = 0; }", + options: ["never", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 12, + endColumn: 13 + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 19, + endColumn: 20 + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 24, + endColumn: 25 + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 26, + endColumn: 27 + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 31, + endColumn: 32 + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 42, + endColumn: 43 + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 51, + endColumn: 52 + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 53, + endColumn: 54 + } + ] + }, // always - classes { @@ -1545,6 +1631,54 @@ ruleTester.run("computed-property-spacing", rule, { } ] }, + { + code: "class A { [ a]; [b ]; [c]; [ a] = 0; [b ] = 0; [c] = 0; }", + output: "class A { [ a ]; [ b ]; [ c ]; [ a ] = 0; [ b ] = 0; [ c ] = 0; }", + options: ["always", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingSpaceBefore", + column: 14, + endColumn: 15 + }, + { + messageId: "missingSpaceAfter", + column: 17, + endColumn: 18 + }, + { + messageId: "missingSpaceAfter", + column: 23, + endColumn: 24 + }, + { + messageId: "missingSpaceBefore", + column: 25, + endColumn: 26 + }, + { + messageId: "missingSpaceBefore", + column: 31, + endColumn: 32 + }, + { + messageId: "missingSpaceAfter", + column: 38, + endColumn: 39 + }, + { + messageId: "missingSpaceAfter", + column: 48, + endColumn: 49 + }, + { + messageId: "missingSpaceBefore", + column: 50, + endColumn: 51 + } + ] + }, // handling of parens and comments { diff --git a/tests/lib/rules/dot-location.js b/tests/lib/rules/dot-location.js index bd56e1fc797..d1206575e4c 100644 --- a/tests/lib/rules/dot-location.js +++ b/tests/lib/rules/dot-location.js @@ -198,6 +198,18 @@ ruleTester.run("dot-location", rule, { code: "obj?.[\nkey]", options: ["property"], parserOptions: { ecmaVersion: 2020 } + }, + + // Private properties + { + code: "class C { #a; foo() { this.\n#a; } }", + options: ["object"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #a; foo() { this\n.#a; } }", + options: ["property"], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -386,6 +398,22 @@ ruleTester.run("dot-location", rule, { options: ["property"], parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "expectedDotBeforeProperty" }] + }, + + // Private properties + { + code: "class C { #a; foo() { this\n.#a; } }", + output: "class C { #a; foo() { this.\n#a; } }", + options: ["object"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedDotAfterObject" }] + }, + { + code: "class C { #a; foo() { this.\n#a; } }", + output: "class C { #a; foo() { this\n.#a; } }", + options: ["property"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedDotBeforeProperty" }] } ] }); diff --git a/tests/lib/rules/dot-notation.js b/tests/lib/rules/dot-notation.js index 39f3a676b0b..fed94c128cf 100644 --- a/tests/lib/rules/dot-notation.js +++ b/tests/lib/rules/dot-notation.js @@ -58,7 +58,8 @@ ruleTester.run("dot-notation", rule, { "a[undefined];", "a[void 0];", "a[b()];", - { code: "a[/(?0)/];", parserOptions: { ecmaVersion: 2018 } } + { code: "a[/(?0)/];", parserOptions: { ecmaVersion: 2018 } }, + { code: "class C { foo() { this['#a'] } }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { diff --git a/tests/lib/rules/func-names.js b/tests/lib/rules/func-names.js index bf930eecfc9..3341df7f016 100644 --- a/tests/lib/rules/func-names.js +++ b/tests/lib/rules/func-names.js @@ -262,6 +262,23 @@ ruleTester.run("func-names", rule, { code: "(function*() {}())", options: ["as-needed", { generators: "never" }], parserOptions: { ecmaVersion: 6 } + }, + + // class fields + { + code: "class C { foo = function() {}; }", + options: ["as-needed"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [foo] = function() {}; }", + options: ["as-needed"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #foo = function() {}; }", + options: ["as-needed"], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -805,6 +822,63 @@ ruleTester.run("func-names", rule, { column: 15, endColumn: 28 }] + }, + + // class fields + { + code: "class C { foo = function() {} }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "unnamed", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 25 + }] + }, + { + code: "class C { [foo] = function() {} }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "unnamed", + data: { name: "method" }, + column: 11, + endColumn: 27 + }] + }, + { + code: "class C { #foo = function() {} }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "unnamed", + data: { name: "private method #foo" }, + column: 11, + endColumn: 26 + }] + }, + { + code: "class C { foo = bar(function() {}) }", + options: ["as-needed"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "unnamed", + data: { name: "function" }, + column: 21, + endColumn: 29 + }] + }, + { + code: "class C { foo = function bar() {} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "named", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 29 + }] } ] }); diff --git a/tests/lib/rules/getter-return.js b/tests/lib/rules/getter-return.js index c4092d86d2d..92032c066fb 100644 --- a/tests/lib/rules/getter-return.js +++ b/tests/lib/rules/getter-return.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); const expectedError = { messageId: "expected", data: { name: "getter 'bar'" } }; const expectedAlwaysError = { messageId: "expectedAlways", data: { name: "getter 'bar'" } }; const options = [{ allowImplicit: true }]; @@ -72,7 +72,8 @@ ruleTester.run("getter-return", rule, { "var foo = { bar: function(){return;} };", "var foo = { bar: function(){return true;} };", "var foo = { get: function () {} }", - "var foo = { get: () => {}};" + "var foo = { get: () => {}};", + "class C { get; foo() {} }" ], invalid: [ @@ -187,7 +188,7 @@ ruleTester.run("getter-return", rule, { code: "Object.defineProperty(foo, 'bar', { get: function getfoo (){}});", errors: [{ messageId: "expected", - data: { name: "method 'getfoo'" }, + data: { name: "method 'get'" }, line: 1, column: 37, endLine: 1, @@ -209,11 +210,11 @@ ruleTester.run("getter-return", rule, { code: "Object.defineProperty(foo, 'bar', { get: () => {}});", errors: [{ messageId: "expected", - data: { name: "arrow function 'get'" }, + data: { name: "method 'get'" }, line: 1, - column: 45, + column: 37, endLine: 1, - endColumn: 47 + endColumn: 42 }] }, { code: "Object.defineProperty(foo, \"bar\", { get: function (){if(bar) {return true;}}});", errors: [{ messageId: "expectedAlways" }] }, diff --git a/tests/lib/rules/grouped-accessor-pairs.js b/tests/lib/rules/grouped-accessor-pairs.js index e94c3a46be2..75aa3a04ff6 100644 --- a/tests/lib/rules/grouped-accessor-pairs.js +++ b/tests/lib/rules/grouped-accessor-pairs.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("grouped-accessor-pairs", rule, { valid: [ @@ -134,7 +134,19 @@ ruleTester.run("grouped-accessor-pairs", rule, { "({ get a(){}, b: 1, set a(foo){}, c: 2, get a(){} })", "({ set a(foo){}, b: 1, set 'a'(bar){}, c: 2, get a(){} })", "class A { get [a](){} b(){} get [a](){} c(){} set [a](foo){} }", - "(class { static set a(foo){} b(){} static get a(){} static c(){} static set a(bar){} })" + "(class { static set a(foo){} b(){} static get a(){} static c(){} static set a(bar){} })", + + // public and private + "class A { get '#abc'(){} b(){} set #abc(foo){} }", + "class A { get #abc(){} b(){} set '#abc'(foo){} }", + { + code: "class A { set '#abc'(foo){} get #abc(){} }", + options: ["getBeforeSet"] + }, + { + code: "class A { set #abc(foo){} get '#abc'(){} }", + options: ["getBeforeSet"] + } ], invalid: [ @@ -172,6 +184,14 @@ ruleTester.run("grouped-accessor-pairs", rule, { code: "class A { static get [a](){} b(){} static set [a](foo){} }", errors: [{ messageId: "notGrouped", data: { formerName: "static getter", latterName: "static setter" }, type: "MethodDefinition", column: 36 }] }, + { + code: "class A { get '#abc'(){} b(){} set '#abc'(foo){} }", + errors: [{ messageId: "notGrouped", data: { formerName: "getter '#abc'", latterName: "setter '#abc'" }, type: "MethodDefinition", column: 32 }] + }, + { + code: "class A { get #abc(){} b(){} set #abc(foo){} }", + errors: [{ messageId: "notGrouped", data: { formerName: "private getter #abc", latterName: "private setter #abc" }, type: "MethodDefinition", column: 30 }] + }, // basic ordering tests with full messages { @@ -214,6 +234,16 @@ ruleTester.run("grouped-accessor-pairs", rule, { options: ["getBeforeSet"], errors: [{ messageId: "invalidOrder", data: { formerName: "static setter", latterName: "static getter" }, type: "MethodDefinition", column: 35 }] }, + { + code: "class A { set '#abc'(foo){} get '#abc'(){} }", + options: ["getBeforeSet"], + errors: [{ messageId: "invalidOrder", data: { latterName: "getter '#abc'", formerName: "setter '#abc'" }, type: "MethodDefinition", column: 29 }] + }, + { + code: "class A { set #abc(foo){} get #abc(){} }", + options: ["getBeforeSet"], + errors: [{ messageId: "invalidOrder", data: { latterName: "private getter #abc", formerName: "private setter #abc" }, type: "MethodDefinition", column: 27 }] + }, // ordering option does not affect the grouping check { @@ -406,6 +436,11 @@ ruleTester.run("grouped-accessor-pairs", rule, { code: "class A { get a(){} a(){} set a(foo){} }", errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "MethodDefinition", column: 27 }] }, + { + code: "class A { get a(){} a; set a(foo){} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "MethodDefinition", column: 24 }] + }, // full location tests { diff --git a/tests/lib/rules/id-denylist.js b/tests/lib/rules/id-denylist.js index 6da179d99ff..4e8248399e4 100644 --- a/tests/lib/rules/id-denylist.js +++ b/tests/lib/rules/id-denylist.js @@ -204,6 +204,18 @@ ruleTester.run("id-denylist", rule, { code: "var foo = { bar: window.baz };", options: ["window"], env: { browser: true } + }, + + // Class fields + { + code: "class C { camelCase; #camelCase; #camelCase2() {} }", + options: ["foo"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { snake_case; #snake_case; #snake_case2() {} }", + options: ["foo"], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -1354,6 +1366,44 @@ ruleTester.run("id-denylist", rule, { type: "Identifier" } ] + }, + + // Class fields + { + code: "class C { camelCase; #camelCase; #camelCase2() {} }", + options: ["camelCase"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "restricted", + data: { name: "camelCase" }, + type: "Identifier" + }, + { + messageId: "restrictedPrivate", + data: { name: "camelCase" }, + type: "PrivateIdentifier" + } + ] + + }, + { + code: "class C { snake_case; #snake_case() {}; #snake_case2() {} }", + options: ["snake_case"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "restricted", + data: { name: "snake_case" }, + type: "Identifier" + }, + { + messageId: "restrictedPrivate", + data: { name: "snake_case" }, + type: "PrivateIdentifier" + } + ] + } ] }); diff --git a/tests/lib/rules/id-length.js b/tests/lib/rules/id-length.js index 99a3957939c..e9a023bcfd1 100644 --- a/tests/lib/rules/id-length.js +++ b/tests/lib/rules/id-length.js @@ -18,7 +18,9 @@ const rule = require("../../../lib/rules/id-length"), const ruleTester = new RuleTester(); const tooShortError = { messageId: "tooShort", type: "Identifier" }; +const tooShortErrorPrivate = { messageId: "tooShortPrivate", type: "PrivateIdentifier" }; const tooLongError = { messageId: "tooLong", type: "Identifier" }; +const tooLongErrorPrivate = { messageId: "tooLongPrivate", type: "PrivateIdentifier" }; ruleTester.run("id-length", rule, { valid: [ @@ -82,7 +84,36 @@ ruleTester.run("id-length", rule, { { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "send$"] }] }, { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "^A", "^Z"] }] }, { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^A", "^BEFORE_", "^Z"] }] }, - { code: "var x = 1 ;", options: [{ min: 3, max: 5, exceptionPatterns: ["[x-z]"] }] } + { code: "var x = 1 ;", options: [{ min: 3, max: 5, exceptionPatterns: ["[x-z]"] }] }, + + // Class Fields + { + code: "class Foo { #xyz() {} }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { xyz = 1 }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { #xyz = 1 }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { #abc() {} }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { abc = 1 }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { #abc = 1 }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { code: "var x = 1;", errors: [tooShortError] }, @@ -486,6 +517,53 @@ ruleTester.run("id-length", rule, { errors: [ tooShortError ] + }, + + // Class Fields + { + code: "class Foo { #x() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooShortErrorPrivate + ] + }, + { + code: "class Foo { x = 1 }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooShortError + ] + }, + { + code: "class Foo { #x = 1 }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooShortErrorPrivate + ] + }, + { + code: "class Foo { #abcdefg() {} }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooLongErrorPrivate + ] + }, + { + code: "class Foo { abcdefg = 1 }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooLongError + ] + }, + { + code: "class Foo { #abcdefg = 1 }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooLongErrorPrivate + ] } ] }); diff --git a/tests/lib/rules/id-match.js b/tests/lib/rules/id-match.js index f733dccbdff..d1fad2ecb8b 100644 --- a/tests/lib/rules/id-match.js +++ b/tests/lib/rules/id-match.js @@ -183,7 +183,36 @@ ruleTester.run("id-match", rule, { options: ["^[^_]+$", { properties: false }] + }, + + // Class Methods + { + code: "class x { foo() {} }", + options: ["^[^_]+$"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class x { #foo() {} }", + options: ["^[^_]+$"], + parserOptions: { ecmaVersion: 2022 } + }, + + // Class Fields + { + code: "class x { _foo = 1; }", + options: ["^[^_]+$", { + classFields: false + }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class x { #_foo = 1; }", + options: ["^[^_]+$", { + classFields: false + }], + parserOptions: { ecmaVersion: 2022 } } + ], invalid: [ { @@ -610,6 +639,59 @@ ruleTester.run("id-match", rule, { type: "Identifier" } ] + }, + + // Class Methods + { + code: "class x { _foo() {} }", + options: ["^[^_]+$"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: "Identifier '_foo' does not match the pattern '^[^_]+$'.", + type: "Identifier" + } + ] + }, + { + code: "class x { #_foo() {} }", + options: ["^[^_]+$"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: "Identifier '#_foo' does not match the pattern '^[^_]+$'.", + type: "PrivateIdentifier" + } + ] + }, + + // Class Fields + { + code: "class x { _foo = 1; }", + options: ["^[^_]+$", { + classFields: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: "Identifier '_foo' does not match the pattern '^[^_]+$'.", + type: "Identifier" + } + ] + }, + { + code: "class x { #_foo = 1; }", + options: ["^[^_]+$", { + classFields: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: "Identifier '#_foo' does not match the pattern '^[^_]+$'.", + type: "PrivateIdentifier" + } + ] } + ] }); diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js index 02517ffaeeb..616f9101506 100644 --- a/tests/lib/rules/indent.js +++ b/tests/lib/rules/indent.js @@ -11627,6 +11627,189 @@ ruleTester.run("indent", rule, { errors: expectedErrors([ [2, 0, 1, "Identifier"] ]) + }, + { + code: unIndent` + class C { + field1; + static field2; + } + `, + output: unIndent` + class C { + field1; + static field2; + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 4, 0, "Keyword"] + ]) + }, + { + code: unIndent` + class C { + field1 + = + 0 + ; + static + field2 + = + 0 + ; + } + `, + output: unIndent` + class C { + field1 + = + 0 + ; + static + field2 + = + 0 + ; + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 8, 0, "Punctuator"], + [4, 12, 0, "Numeric"], + [5, 12, 0, "Punctuator"], + [6, 4, 0, "Keyword"], + [7, 8, 0, "Identifier"], + [8, 12, 0, "Punctuator"], + [9, 16, 0, "Numeric"], + [10, 16, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + [ + field1 + ] + = + 0 + ; + static + [ + field2 + ] + = + 0 + ; + [ + field3 + ] = + 0; + [field4] = + 0; + } + `, + output: unIndent` + class C { + [ + field1 + ] + = + 0 + ; + static + [ + field2 + ] + = + 0 + ; + [ + field3 + ] = + 0; + [field4] = + 0; + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Punctuator"], + [3, 8, 0, "Identifier"], + [4, 4, 0, "Punctuator"], + [5, 8, 0, "Punctuator"], + [6, 12, 0, "Numeric"], + [7, 12, 0, "Punctuator"], + [8, 4, 0, "Keyword"], + [9, 4, 0, "Punctuator"], + [10, 8, 0, "Identifier"], + [11, 4, 0, "Punctuator"], + [12, 8, 0, "Punctuator"], + [13, 12, 0, "Numeric"], + [14, 12, 0, "Punctuator"], + [15, 4, 0, "Punctuator"], + [16, 8, 0, "Identifier"], + [17, 4, 0, "Punctuator"], + [18, 8, 0, "Numeric"], + [19, 4, 0, "Punctuator"], + [20, 8, 0, "Numeric"] + ]) + }, + { + code: unIndent` + class C { + field1 = ( + foo + + bar + ); + } + `, + output: unIndent` + class C { + field1 = ( + foo + + bar + ); + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 8, 0, "Identifier"], + [5, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + #aaa + foo() { + return this.#aaa + } + } + `, + output: unIndent` + class C { + #aaa + foo() { + return this.#aaa + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "PrivateIdentifier"], + [3, 4, 0, "Identifier"], + [4, 8, 0, "Keyword"], + [5, 4, 0, "Punctuator"] + ]) } ] }); diff --git a/tests/lib/rules/keyword-spacing.js b/tests/lib/rules/keyword-spacing.js index 64ee5a43474..821a2b262fd 100644 --- a/tests/lib/rules/keyword-spacing.js +++ b/tests/lib/rules/keyword-spacing.js @@ -721,11 +721,17 @@ ruleTester.run("keyword-spacing", rule, { { code: "class A { a() {} get [b]() {} }", options: [override("get", BOTH)], parserOptions: { ecmaVersion: 6 } }, { code: "({ get[b]() {} })", options: [override("get", NEITHER)], parserOptions: { ecmaVersion: 6 } }, { code: "class A { a() {}get[b]() {} }", options: [override("get", NEITHER)], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { a; get #b() {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a;get#b() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, // not conflict with `comma-spacing` { code: "({ a,get [b]() {} })", parserOptions: { ecmaVersion: 6 } }, { code: "({ a, get[b]() {} })", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + // not conflict with `semi-spacing` + { code: "class A { ;get #b() {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { ; get#b() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, + //---------------------------------------------------------------------- // if //---------------------------------------------------------------------- @@ -905,11 +911,17 @@ ruleTester.run("keyword-spacing", rule, { { code: "class A { a() {} set [b](value) {} }", options: [override("set", BOTH)], parserOptions: { ecmaVersion: 6 } }, { code: "({ set[b](value) {} })", options: [override("set", NEITHER)], parserOptions: { ecmaVersion: 6 } }, { code: "class A { a() {}set[b](value) {} }", options: [override("set", NEITHER)], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { a; set #b(value) {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a;set#b(value) {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, // not conflict with `comma-spacing` { code: "({ a,set [b](value) {} })", parserOptions: { ecmaVersion: 6 } }, { code: "({ a, set[b](value) {} })", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + // not conflict with `semi-spacing` + { code: "class A { ;set #b(value) {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { ; set#b(value) {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, + //---------------------------------------------------------------------- // static //---------------------------------------------------------------------- @@ -918,6 +930,10 @@ ruleTester.run("keyword-spacing", rule, { { code: "class A { a() {}static[b]() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, { code: "class A { a() {} static [b]() {} }", options: [override("static", BOTH)], parserOptions: { ecmaVersion: 6 } }, { code: "class A { a() {}static[b]() {} }", options: [override("static", NEITHER)], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { a; static [b]; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a;static[b]; }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a; static #b; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a;static#b; }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, // not conflict with `generator-star-spacing` { code: "class A { static* [a]() {} }", parserOptions: { ecmaVersion: 6 } }, @@ -2453,6 +2469,19 @@ ruleTester.run("keyword-spacing", rule, { parserOptions: { ecmaVersion: 6 }, errors: unexpectedBeforeAndAfter("get") }, + { + code: "class A { a;get#b() {} }", + output: "class A { a;get #b() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedAfter("get") + }, + { + code: "class A { a; get #b() {} }", + output: "class A { a; get#b() {} }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedAfter("get") + }, //---------------------------------------------------------------------- // if @@ -2833,6 +2862,19 @@ ruleTester.run("keyword-spacing", rule, { parserOptions: { ecmaVersion: 6 }, errors: unexpectedBeforeAndAfter("set") }, + { + code: "class A { a;set#b(x) {} }", + output: "class A { a;set #b(x) {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedAfter("set") + }, + { + code: "class A { a; set #b(x) {} }", + output: "class A { a; set#b(x) {} }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedAfter("set") + }, //---------------------------------------------------------------------- // static @@ -2878,6 +2920,32 @@ ruleTester.run("keyword-spacing", rule, { parserOptions: { ecmaVersion: 6 }, errors: unexpectedBeforeAndAfter("static") }, + { + code: "class A { a;static[b]; }", + output: "class A { a;static [b]; }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedAfter("static") + }, + { + code: "class A { a; static [b]; }", + output: "class A { a; static[b]; }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedAfter("static") + }, + { + code: "class A { a;static#b; }", + output: "class A { a;static #b; }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedAfter("static") + }, + { + code: "class A { a; static #b; }", + output: "class A { a; static#b; }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedAfter("static") + }, //---------------------------------------------------------------------- // super diff --git a/tests/lib/rules/lines-between-class-members.js b/tests/lib/rules/lines-between-class-members.js index e4b1c0c092f..da83a7a6f12 100644 --- a/tests/lib/rules/lines-between-class-members.js +++ b/tests/lib/rules/lines-between-class-members.js @@ -22,7 +22,7 @@ const neverError = { messageId: "never" }; // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("lines-between-class-members", rule, { valid: [ @@ -46,6 +46,8 @@ ruleTester.run("lines-between-class-members", rule, { "class foo{ bar(){}\n\n;;baz(){}}", "class foo{ bar(){};\n\nbaz(){}}", + "class C {\naaa;\n\n#bbb;\n\nccc(){}\n\n#ddd(){}\n}", + { code: "class foo{ bar(){}\nbaz(){}}", options: ["never"] }, { code: "class foo{ bar(){}\n/*comments*/baz(){}}", options: ["never"] }, { code: "class foo{ bar(){}\n//comments\nbaz(){}}", options: ["never"] }, @@ -58,7 +60,8 @@ ruleTester.run("lines-between-class-members", rule, { { code: "class foo{ bar(){}\n\n//comments\nbaz(){}}", options: ["always"] }, { code: "class foo{ bar(){}\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] }, - { code: "class foo{ bar(){\n}\n\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] } + { code: "class foo{ bar(){\n}\n\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] }, + { code: "class foo{\naaa;\n#bbb;\nccc(){\n}\n\n#ddd(){\n}\n}", options: ["always", { exceptAfterSingleLine: true }] } ], invalid: [ { @@ -141,6 +144,26 @@ ruleTester.run("lines-between-class-members", rule, { output: "class A {\nfoo() {}\n\n/* comment */;\n;\nbar() {}\n}", options: ["always"], errors: [alwaysError] + }, { + code: "class C {\nfield1\nfield2\n}", + output: "class C {\nfield1\n\nfield2\n}", + options: ["always"], + errors: [alwaysError] + }, { + code: "class C {\n#field1\n#field2\n}", + output: "class C {\n#field1\n\n#field2\n}", + options: ["always"], + errors: [alwaysError] + }, { + code: "class C {\nfield1\n\nfield2\n}", + output: "class C {\nfield1\nfield2\n}", + options: ["never"], + errors: [neverError] + }, { + code: "class C {\nfield1 = () => {\n}\nfield2\nfield3\n}", + output: "class C {\nfield1 = () => {\n}\n\nfield2\nfield3\n}", + options: ["always", { exceptAfterSingleLine: true }], + errors: [alwaysError] } ] }); diff --git a/tests/lib/rules/max-statements.js b/tests/lib/rules/max-statements.js index fa3d3d1f1bd..fd939e592cc 100644 --- a/tests/lib/rules/max-statements.js +++ b/tests/lib/rules/max-statements.js @@ -127,7 +127,7 @@ ruleTester.run("max-statements", rule, { code: "var foo = { thing: () => { var bar = 1; var baz = 2; var baz2; } }", options: [2], parserOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "exceed", data: { name: "Arrow function 'thing'", count: "3", max: 2 } }] + errors: [{ messageId: "exceed", data: { name: "Method 'thing'", count: "3", max: 2 } }] }, { code: "var foo = { thing: function() { var bar = 1; var baz = 2; var baz2; } }", diff --git a/tests/lib/rules/no-dupe-class-members.js b/tests/lib/rules/no-dupe-class-members.js index a1a534d2da9..a37784bc3fa 100644 --- a/tests/lib/rules/no-dupe-class-members.js +++ b/tests/lib/rules/no-dupe-class-members.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("no-dupe-class-members", rule, { valid: [ @@ -51,7 +51,12 @@ ruleTester.run("no-dupe-class-members", rule, { "class A { [-1]() {} ['-1']() {} }", // not supported by this rule - "class A { [foo]() {} [foo]() {} }" + "class A { [foo]() {} [foo]() {} }", + + // private and public + "class A { foo; static foo; }", + "class A { foo; #foo; }", + "class A { '#foo'; #foo; }" ], invalid: [ { @@ -217,6 +222,17 @@ ruleTester.run("no-dupe-class-members", rule, { errors: [ { type: "MethodDefinition", line: 1, column: 29, messageId: "unexpected", data: { name: "foo" } } ] + }, + { + code: "class A { foo; foo; }", + errors: [ + { type: "PropertyDefinition", line: 1, column: 16, messageId: "unexpected", data: { name: "foo" } } + ] } + + /* + * This is syntax error + * { code: "class A { #foo; #foo; }" } + */ ] }); diff --git a/tests/lib/rules/no-eval.js b/tests/lib/rules/no-eval.js index bc8d38471bb..2f6f7f925fb 100644 --- a/tests/lib/rules/no-eval.js +++ b/tests/lib/rules/no-eval.js @@ -48,6 +48,8 @@ ruleTester.run("no-eval", rule, { "var obj = {}; obj.foo = function() { this.eval('foo'); }", { code: "class A { foo() { this.eval(); } }", parserOptions: { ecmaVersion: 6 } }, { code: "class A { static foo() { this.eval(); } }", parserOptions: { ecmaVersion: 6 } }, + { code: "class A { field = this.eval(); }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { field = () => this.eval(); }", parserOptions: { ecmaVersion: 2022 } }, // Allows indirect eval { code: "(0, eval)('foo')", options: [{ allowIndirect: true }] }, @@ -123,6 +125,13 @@ ruleTester.run("no-eval", rule, { parserOptions: { ecmaVersion: 2020 }, globals: { window: "readonly" }, errors: [{ messageId: "unexpected" }] + }, + + // Class fields + { + code: "class C { [this.eval('foo')] }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpected" }] } ] }); diff --git a/tests/lib/rules/no-extra-semi.js b/tests/lib/rules/no-extra-semi.js index 913290aaf85..838925e8caf 100644 --- a/tests/lib/rules/no-extra-semi.js +++ b/tests/lib/rules/no-extra-semi.js @@ -38,6 +38,8 @@ ruleTester.run("no-extra-semi", rule, { { code: "class A { a() { this; } }", parserOptions: { ecmaVersion: 6 } }, { code: "var A = class { a() { this; } };", parserOptions: { ecmaVersion: 6 } }, { code: "class A { } a;", parserOptions: { ecmaVersion: 6 } }, + { code: "class A { field; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { field = 0; }", parserOptions: { ecmaVersion: 2022 } }, // modules { code: "export const x = 42;", parserOptions: { ecmaVersion: 6, sourceType: "module" } }, @@ -157,6 +159,12 @@ ruleTester.run("no-extra-semi", rule, { output: "class A { a() {} get b() {} }", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", type: "Punctuator", column: 17 }] + }, + { + code: "class A { field;; }", + output: "class A { field; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpected", type: "Punctuator", column: 17 }] } ] }); diff --git a/tests/lib/rules/no-invalid-this.js b/tests/lib/rules/no-invalid-this.js index 3b19d63696f..fe69e7c12b7 100644 --- a/tests/lib/rules/no-invalid-this.js +++ b/tests/lib/rules/no-invalid-this.js @@ -724,6 +724,8 @@ const patterns = [ valid: [NORMAL], invalid: [USE_STRICT, IMPLIED_STRICT, MODULES] }, + + // Logical assignments { code: "obj.method &&= function () { console.log(this); z(x => console.log(x, this)); }", parserOptions: { ecmaVersion: 2021 }, @@ -741,6 +743,39 @@ const patterns = [ parserOptions: { ecmaVersion: 2021 }, valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], invalid: [] + }, + + // Class fields. + { + code: "class C { field = console.log(this); }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { field = z(x => console.log(x, this)); }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { field = function () { console.log(this); z(x => console.log(x, this)); }; }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { #field = function () { console.log(this); z(x => console.log(x, this)); }; }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { [this.foo]; }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL], // the global this in non-strict mode is OK. + invalid: [USE_STRICT, IMPLIED_STRICT, MODULES], + errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] } ]; diff --git a/tests/lib/rules/no-multi-assign.js b/tests/lib/rules/no-multi-assign.js index c534f55b33f..23f127db90f 100644 --- a/tests/lib/rules/no-multi-assign.js +++ b/tests/lib/rules/no-multi-assign.js @@ -53,7 +53,11 @@ ruleTester.run("no-mutli-assign", rule, { { code: "export let a, b;", parserOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "export let a,\n b = 0;", parserOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "const x = {};const y = {};x.one = y.one = 1;", options: [{ ignoreNonDeclaration: true }], parserOptions: { ecmaVersion: 6 } }, - { code: "let a, b;a = b = 1", options: [{ ignoreNonDeclaration: true }], parserOptions: { ecmaVersion: 6 } } + { code: "let a, b;a = b = 1", options: [{ ignoreNonDeclaration: true }], parserOptions: { ecmaVersion: 6 } }, + { + code: "class C { [foo = 0] = 0 }", + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ @@ -172,6 +176,21 @@ ruleTester.run("no-mutli-assign", rule, { errors: [ errorAt(1, 11, "AssignmentExpression") ] + }, + { + code: "class C { field = foo = 0 }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + errorAt(1, 19, "AssignmentExpression") + ] + }, + { + code: "class C { field = foo = 0 }", + options: [{ ignoreNonDeclaration: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + errorAt(1, 19, "AssignmentExpression") + ] } ] }); diff --git a/tests/lib/rules/no-proto.js b/tests/lib/rules/no-proto.js index 3e0e7e06155..c9b71357156 100644 --- a/tests/lib/rules/no-proto.js +++ b/tests/lib/rules/no-proto.js @@ -23,7 +23,8 @@ ruleTester.run("no-proto", rule, { "var a = test[__proto__];", "var __proto__ = null;", { code: "foo[`__proto`] = null;", parserOptions: { ecmaVersion: 6 } }, - { code: "foo[`__proto__\n`] = null;", parserOptions: { ecmaVersion: 6 } } + { code: "foo[`__proto__\n`] = null;", parserOptions: { ecmaVersion: 6 } }, + { code: "class C { #__proto__; foo() { this.#__proto__; } }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { code: "var a = test.__proto__;", errors: [{ messageId: "unexpectedProto", type: "MemberExpression" }] }, diff --git a/tests/lib/rules/no-prototype-builtins.js b/tests/lib/rules/no-prototype-builtins.js index a65b54d4d63..36287684e55 100644 --- a/tests/lib/rules/no-prototype-builtins.js +++ b/tests/lib/rules/no-prototype-builtins.js @@ -43,6 +43,7 @@ ruleTester.run("no-prototype-builtins", rule, { { code: "foo?.['propertyIsEnumerabl']('bar')", parserOptions: { ecmaVersion: 2020 } }, "foo[1]('bar')", "foo[null]('bar')", + { code: "class C { #hasOwnProperty; foo() { obj.#hasOwnProperty('bar'); } }", parserOptions: { ecmaVersion: 2022 } }, // out of scope for this rule "foo['hasOwn' + 'Property']('bar')", diff --git a/tests/lib/rules/no-restricted-properties.js b/tests/lib/rules/no-restricted-properties.js index c9fb2b72afc..7c557bcdc06 100644 --- a/tests/lib/rules/no-restricted-properties.js +++ b/tests/lib/rules/no-restricted-properties.js @@ -169,6 +169,10 @@ ruleTester.run("no-restricted-properties", rule, { code: "function qux([, bar] = foo) {}", options: [{ object: "foo", property: "1" }], parserOptions: { ecmaVersion: 6 } + }, { + code: "class C { #foo; foo() { this.#foo; } }", + options: [{ property: "#foo" }], + parserOptions: { ecmaVersion: 2022 } } ], @@ -530,6 +534,18 @@ ruleTester.run("no-restricted-properties", rule, { }, type: "ObjectPattern" }] + }, { + code: "obj['#foo']", + options: [{ property: "#foo" }], + errors: [{ + messageId: "restrictedProperty", + data: { + objectName: "", + propertyName: "#foo", + message: "" + }, + type: "MemberExpression" + }] } ] }); diff --git a/tests/lib/rules/no-self-assign.js b/tests/lib/rules/no-self-assign.js index 5a9bb6fcfed..04aa2dee402 100644 --- a/tests/lib/rules/no-self-assign.js +++ b/tests/lib/rules/no-self-assign.js @@ -79,6 +79,14 @@ ruleTester.run("no-self-assign", rule, { { code: "this.x = this.x", options: [{ props: false }] + }, + { + code: "class C { #field; foo() { this['#field'] = this.#field; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #field; foo() { this.#field = this['#field']; } }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -147,6 +155,18 @@ ruleTester.run("no-self-assign", rule, { code: "a.b = a?.b", parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "selfAssignment", data: { name: "a?.b" } }] + }, + + // Private members + { + code: "class C { #field; foo() { this.#field = this.#field; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "selfAssignment", data: { name: "this.#field" } }] + }, + { + code: "class C { #field; foo() { [this.#field] = [this.#field]; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "selfAssignment", data: { name: "this.#field" } }] } ] }); diff --git a/tests/lib/rules/no-self-compare.js b/tests/lib/rules/no-self-compare.js index 85bc1e21c07..ad4d30a892f 100644 --- a/tests/lib/rules/no-self-compare.js +++ b/tests/lib/rules/no-self-compare.js @@ -23,7 +23,15 @@ ruleTester.run("no-self-compare", rule, { "if (x === y) { }", "if (1 === 2) { }", "y=x*x", - "foo.bar.baz === foo.bar.qux" + "foo.bar.baz === foo.bar.qux", + { + code: "class C { #field; foo() { this.#field === this['#field']; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #field; foo() { this['#field'] === this.#field; } }", + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { code: "if (x === x) { }", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }, @@ -39,6 +47,11 @@ ruleTester.run("no-self-compare", rule, { { code: "x < x", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }, { code: "x >= x", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }, { code: "x <= x", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }, - { code: "foo.bar().baz.qux >= foo.bar ().baz .qux", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] } + { code: "foo.bar().baz.qux >= foo.bar ().baz .qux", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }, + { + code: "class C { #field; foo() { this.#field === this.#field; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] + } ] }); diff --git a/tests/lib/rules/no-setter-return.js b/tests/lib/rules/no-setter-return.js index 0c64e8b4811..ab5b196b9f3 100644 --- a/tests/lib/rules/no-setter-return.js +++ b/tests/lib/rules/no-setter-return.js @@ -39,7 +39,7 @@ function error(column, type = "ReturnStatement") { // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("no-setter-return", rule, { valid: [ @@ -114,6 +114,7 @@ ruleTester.run("no-setter-return", rule, { "class A { static set(val) { return 1; } }", "({ set: set = function set(val) { return 1; } } = {})", "({ set: set = (val) => 1 } = {})", + "class C { set; foo() { return 1; } }", // not returning from the setter "({ set foo(val) { function foo(val) { return 1; } } })", diff --git a/tests/lib/rules/no-shadow.js b/tests/lib/rules/no-shadow.js index dc2cc63c4de..036c6526258 100644 --- a/tests/lib/rules/no-shadow.js +++ b/tests/lib/rules/no-shadow.js @@ -57,7 +57,8 @@ ruleTester.run("no-shadow", rule, { { code: "function foo() { var top = 0; }", env: { browser: true } }, { code: "var Object = 0;", options: [{ builtinGlobals: true }] }, { code: "var top = 0;", options: [{ builtinGlobals: true }], env: { browser: true } }, - { code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", options: [{ allow: ["cb"] }] } + { code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", options: [{ allow: ["cb"] }] }, + { code: "class C { foo; foo() { let foo; } }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { diff --git a/tests/lib/rules/no-this-before-super.js b/tests/lib/rules/no-this-before-super.js index 7a947c00b1c..7b9a8a48a96 100644 --- a/tests/lib/rules/no-this-before-super.js +++ b/tests/lib/rules/no-this-before-super.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("no-this-before-super", rule, { valid: [ @@ -97,7 +97,13 @@ ruleTester.run("no-this-before-super", rule, { } } } - ` + `, + + // Class field initializers are always evaluated after `super()`. + "class C { field = this.toString(); }", + "class C extends B { field = this.foo(); }", + "class C extends B { field = this.foo(); constructor() { super(); } }", + "class C extends B { field = this.foo(); constructor() { } }" // < in this case, initializers are never evaluated. ], invalid: [ diff --git a/tests/lib/rules/no-throw-literal.js b/tests/lib/rules/no-throw-literal.js index 3855b58fdfb..d50b2cea2b6 100644 --- a/tests/lib/rules/no-throw-literal.js +++ b/tests/lib/rules/no-throw-literal.js @@ -30,6 +30,7 @@ ruleTester.run("no-throw-literal", rule, { "throw new foo();", // NewExpression "throw foo.bar;", // MemberExpression "throw foo[bar];", // MemberExpression + { code: "class C { #field; foo() { throw foo.#field; } }", parserOptions: { ecmaVersion: 2022 } }, // MemberExpression "throw foo = new Error();", // AssignmentExpression with the `=` operator { code: "throw foo.bar ||= 'literal'", parserOptions: { ecmaVersion: 2021 } }, // AssignmentExpression with a logical operator { code: "throw foo[bar] ??= 'literal'", parserOptions: { ecmaVersion: 2021 } }, // AssignmentExpression with a logical operator diff --git a/tests/lib/rules/no-undef-init.js b/tests/lib/rules/no-undef-init.js index b57b9cdc0bb..69d0566caf8 100644 --- a/tests/lib/rules/no-undef-init.js +++ b/tests/lib/rules/no-undef-init.js @@ -148,6 +148,32 @@ ruleTester.run("no-undef-init", rule, { output: "let a//comment\n, b;", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unnecessaryUndefinedInit", data: { name: "a" }, type: "VariableDeclarator" }] + }, + + // Class fields + { + code: "class C { field = undefined; }", + output: "class C { field; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unnecessaryUndefinedInit", data: { name: "field" }, type: "PropertyDefinition" }] + }, + { + code: "class C { field = undefined }", + output: "class C { field }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unnecessaryUndefinedInit", data: { name: "field" }, type: "PropertyDefinition" }] + }, + { + code: "class C { #field = undefined; }", + output: "class C { #field; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unnecessaryUndefinedInit", data: { name: "#field" }, type: "PropertyDefinition" }] + }, + { + code: "class C { '#field' = undefined; }", + output: "class C { '#field'; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unnecessaryUndefinedInit", data: { name: "'#field'" }, type: "PropertyDefinition" }] } ] }); diff --git a/tests/lib/rules/no-underscore-dangle.js b/tests/lib/rules/no-underscore-dangle.js index 89f20835de2..c83a5b3fe28 100644 --- a/tests/lib/rules/no-underscore-dangle.js +++ b/tests/lib/rules/no-underscore-dangle.js @@ -69,7 +69,9 @@ ruleTester.run("no-underscore-dangle", rule, { { code: "function foo([_bar] = []) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } }, { code: "function foo( { _bar }) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } }, { code: "function foo( { _bar = 0 } = {}) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(...[_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 2016 } } + { code: "function foo(...[_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 2016 } }, + { code: "class foo { _field; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class foo { #_field; }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { code: "var _foo = 1", errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" }, type: "VariableDeclarator" }] }, @@ -96,8 +98,17 @@ ruleTester.run("no-underscore-dangle", rule, { { code: "const foo = (_bar = 0) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "AssignmentPattern" }] }, { code: "function foo(..._bar) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] }, { code: "const foo = { onClick(..._bar) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] }, - { code: "const foo = (..._bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] } - - + { code: "const foo = (..._bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] }, + { + code: "class foo { #_bar() {} }", + options: [{ enforceInMethodNames: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "#_bar" } }] + }, { + code: "class foo { #bar_() {} }", + options: [{ enforceInMethodNames: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "#bar_" } }] + } ] }); diff --git a/tests/lib/rules/no-unexpected-multiline.js b/tests/lib/rules/no-unexpected-multiline.js index 83c7bf67570..786931b338a 100644 --- a/tests/lib/rules/no-unexpected-multiline.js +++ b/tests/lib/rules/no-unexpected-multiline.js @@ -140,6 +140,28 @@ ruleTester.run("no-unexpected-multiline", rule, { { code: "var a = b?.\n [a, b, c].forEach(doSomething)", parserOptions: { ecmaVersion: 2020 } + }, + + // Class fields + { + code: "class C { field1\n[field2]; }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { field1\n*gen() {} }", + parserOptions: { ecmaVersion: 2022 } + }, + { + + // ArrowFunctionExpression doesn't connect to computed properties. + code: "class C { field1 = () => {}\n[field2]; }", + parserOptions: { ecmaVersion: 2022 } + }, + { + + // ArrowFunctionExpression doesn't connect to binary operators. + code: "class C { field1 = () => {}\n*gen() {} }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -321,6 +343,36 @@ ruleTester.run("no-unexpected-multiline", rule, { messageId: "taggedTemplate" } ] + }, + + // Class fields + { + code: "class C { field1 = obj\n[field2]; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + line: 2, + column: 1, + endLine: 2, + endColumn: 2, + messageId: "property" + } + ] + }, + { + code: "class C { field1 = function() {}\n[field2]; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + line: 2, + column: 1, + endLine: 2, + endColumn: 2, + messageId: "property" + } + ] } + + // "class C { field1 = obj\n*gen() {} }" is syntax error: Unexpected token '{' ] }); diff --git a/tests/lib/rules/no-unreachable.js b/tests/lib/rules/no-unreachable.js index e1609c8a811..01589a75cde 100644 --- a/tests/lib/rules/no-unreachable.js +++ b/tests/lib/rules/no-unreachable.js @@ -65,6 +65,22 @@ ruleTester.run("no-unreachable", rule, { parserOptions: { ecmaVersion: 6 } + }, + { + code: "class C { foo = reachable; }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = reachable; constructor() {} }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C extends B { foo = reachable; }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C extends B { foo = reachable; constructor() { super(); } }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -302,6 +318,37 @@ ruleTester.run("no-unreachable", rule, { endColumn: 22 } ] + }, + + /* + * If `extends` exists, constructor exists, and the constructor doesn't + * contain `super()`, then the fields are unreachable because the + * evaluation of `super()` initializes fields in that case. + * In most cases, such an instantiation throws runtime errors, but + * doesn't throw if the constructor returns a value. + */ + { + code: "class C extends B { foo; constructor() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unreachableCode", column: 21, endColumn: 25 }] + }, + { + code: "class C extends B { foo = unreachable + code; constructor() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unreachableCode", column: 21, endColumn: 46 }] + }, + { + code: "class C extends B { foo; bar; constructor() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unreachableCode", column: 21, endColumn: 30 }] + }, + { + code: "class C extends B { foo; constructor() {} bar; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unreachableCode", column: 21, endColumn: 25 }, + { messageId: "unreachableCode", column: 43, endColumn: 47 } + ] } ] }); diff --git a/tests/lib/rules/no-useless-call.js b/tests/lib/rules/no-useless-call.js index 528f2f1b129..43f9b7d71ea 100644 --- a/tests/lib/rules/no-useless-call.js +++ b/tests/lib/rules/no-useless-call.js @@ -50,6 +50,12 @@ ruleTester.run("no-useless-call", rule, { { code: "obj?.foo.bar.call(obj.foo, 1, 2);", parserOptions: { ecmaVersion: 2020 } + }, + + // Private members + { + code: "class C { #call; wrap(foo) { foo.#call(undefined, 1, 2); } }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ diff --git a/tests/lib/rules/no-useless-computed-key.js b/tests/lib/rules/no-useless-computed-key.js index 100899c880e..a7bf323cf38 100644 --- a/tests/lib/rules/no-useless-computed-key.js +++ b/tests/lib/rules/no-useless-computed-key.js @@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/no-useless-computed-key"), // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("no-useless-computed-key", rule, { valid: [ @@ -41,6 +41,7 @@ ruleTester.run("no-useless-computed-key", rule, { { code: "(class { ['x']() {} })", options: [{ enforceForClassMembers: false }] }, { code: "class Foo { static ['constructor']() {} }", options: [{ enforceForClassMembers: false }] }, { code: "class Foo { ['prototype']() {} }", options: [{ enforceForClassMembers: false }] }, + { code: "class Foo { a }", options: [{ enforceForClassMembers: true }] }, /* * Well-known browsers throw syntax error bigint literals on property names, @@ -478,6 +479,42 @@ ruleTester.run("no-useless-computed-key", rule, { data: { property: "'prototype'" }, type: "MethodDefinition" }] + }, { + code: "class Foo { ['0'] }", + output: "class Foo { '0' }", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'0'" }, + type: "PropertyDefinition" + }] + }, { + code: "class Foo { ['0'] = 0 }", + output: "class Foo { '0' = 0 }", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'0'" }, + type: "PropertyDefinition" + }] + }, { + code: "class Foo { static[0] }", + output: "class Foo { static 0 }", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "0" }, + type: "PropertyDefinition" + }] + }, { + code: "class Foo { ['#foo'] }", + output: "class Foo { '#foo' }", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'#foo'" }, + type: "PropertyDefinition" + }] } ] }); diff --git a/tests/lib/rules/operator-assignment.js b/tests/lib/rules/operator-assignment.js index 1d07b0cd8ed..8f814840e29 100644 --- a/tests/lib/rules/operator-assignment.js +++ b/tests/lib/rules/operator-assignment.js @@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/operator-assignment"), // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); const EXPECTED_OPERATOR_ASSIGNMENT = [{ messageId: "replaced", type: "AssignmentExpression" }]; const UNEXPECTED_OPERATOR_ASSIGNMENT = [{ messageId: "unexpected", type: "AssignmentExpression" }]; @@ -85,6 +85,7 @@ ruleTester.run("operator-assignment", rule, { code: "this.x = foo.this.x + y", options: ["always"] }, + "const foo = 0; class C { foo = foo + 1; }", // does not check logical operators { diff --git a/tests/lib/rules/operator-linebreak.js b/tests/lib/rules/operator-linebreak.js index 1eeb9f5790e..e94d48c116b 100644 --- a/tests/lib/rules/operator-linebreak.js +++ b/tests/lib/rules/operator-linebreak.js @@ -98,6 +98,16 @@ ruleTester.run("operator-linebreak", rule, { code: "a ??= \n b", options: ["after", { overrides: { "??": "before" } }], parserOptions: { ecmaVersion: 2021 } + }, + + { + code: "class C { foo =\n0 }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n= 0 }", + options: ["before"], + parserOptions: { ecmaVersion: 2022 } } ], @@ -770,6 +780,52 @@ ruleTester.run("operator-linebreak", rule, { endLine: 2, endColumn: 4 }] + }, + + { + code: "class C { a\n= 0; }", + output: "class C { a =\n0; }", + options: ["after"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "operatorAtEnd", + data: { operator: "=" }, + type: "PropertyDefinition", + line: 2, + column: 1, + endLine: 2, + endColumn: 2 + }] + }, + { + code: "class C { a =\n0; }", + output: "class C { a\n= 0; }", + options: ["before"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "operatorAtBeginning", + data: { operator: "=" }, + type: "PropertyDefinition", + line: 1, + column: 13, + endLine: 1, + endColumn: 14 + }] + }, + { + code: "class C { a =\n0; }", + output: "class C { a =0; }", + options: ["none"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noLinebreak", + data: { operator: "=" }, + type: "PropertyDefinition", + line: 1, + column: 13, + endLine: 1, + endColumn: 14 + }] } ] }); diff --git a/tests/lib/rules/padded-blocks.js b/tests/lib/rules/padded-blocks.js index 1e15da4d245..76cfcda51cc 100644 --- a/tests/lib/rules/padded-blocks.js +++ b/tests/lib/rules/padded-blocks.js @@ -74,6 +74,9 @@ ruleTester.run("padded-blocks", rule, { { code: "class A{\nfoo(){}\n}", options: ["never"], parserOptions: { ecmaVersion: 6 } }, { code: "class A{\nfoo(){}\n}", options: [{ classes: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "class A{\n\nfoo;\n\n}", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A{\nfoo;\n}", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + // Ignore block statements if not configured { code: "{\na();\n}", options: [{ switches: "always" }] }, { code: "{\n\na();\n\n}", options: [{ switches: "never" }] }, @@ -748,6 +751,19 @@ ruleTester.run("padded-blocks", rule, { output: "function foo() { /* a\n */ /* b\n */\n\n bar;\n\n/* c\n *//* d\n */}", options: ["always"], errors: [{ messageId: "alwaysPadBlock" }, { messageId: "alwaysPadBlock" }] + }, + { + code: "class A{\nfoo;\n}", + output: "class A{\n\nfoo;\n\n}", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "alwaysPadBlock" }, { messageId: "alwaysPadBlock" }] + }, + { + code: "class A{\n\nfoo;\n\n}", + output: "class A{\nfoo;\n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "neverPadBlock" }, { messageId: "neverPadBlock" }] } ] }); diff --git a/tests/lib/rules/prefer-exponentiation-operator.js b/tests/lib/rules/prefer-exponentiation-operator.js index 7bccdd64b43..2de358e2c8f 100644 --- a/tests/lib/rules/prefer-exponentiation-operator.js +++ b/tests/lib/rules/prefer-exponentiation-operator.js @@ -40,7 +40,7 @@ function invalid(code, output) { // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("prefer-exponentiation-operator", rule, { valid: [ @@ -78,7 +78,9 @@ ruleTester.run("prefer-exponentiation-operator", rule, { globalThis.Math.pow(a, b) `, env: { es2020: true } - } + }, + + "class C { #pow; foo() { Math.#pow(a, b); } }" ], invalid: [ diff --git a/tests/lib/rules/prefer-numeric-literals.js b/tests/lib/rules/prefer-numeric-literals.js index 39cc11a2425..e3cacaa1318 100644 --- a/tests/lib/rules/prefer-numeric-literals.js +++ b/tests/lib/rules/prefer-numeric-literals.js @@ -58,6 +58,10 @@ ruleTester.run("prefer-numeric-literals", rule, { { code: "parseInt(1n, 2);", parserOptions: { ecmaVersion: 2020 } + }, + { + code: "class C { #parseInt; foo() { Number.#parseInt(\"111110111\", 2); } }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ diff --git a/tests/lib/rules/prefer-object-spread.js b/tests/lib/rules/prefer-object-spread.js index 91bf08a2f74..d80275c0df6 100644 --- a/tests/lib/rules/prefer-object-spread.js +++ b/tests/lib/rules/prefer-object-spread.js @@ -76,6 +76,10 @@ ruleTester.run("prefer-object-spread", rule, { `, env: { es2020: true } }, + { + code: "class C { #assign; foo() { Object.#assign({}, foo); } }", + parserOptions: { ecmaVersion: 2022 } + }, // ignore Object.assign() with > 1 arguments if any of the arguments is an object expression with a getter/setter "Object.assign({ get a() {} }, {})", diff --git a/tests/lib/rules/prefer-promise-reject-errors.js b/tests/lib/rules/prefer-promise-reject-errors.js index b31ec33622b..508fc8cc963 100644 --- a/tests/lib/rules/prefer-promise-reject-errors.js +++ b/tests/lib/rules/prefer-promise-reject-errors.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("prefer-promise-reject-errors", rule, { @@ -54,7 +54,11 @@ ruleTester.run("prefer-promise-reject-errors", rule, { "Promise.reject(foo = new Error())", "Promise.reject(foo ||= 5)", "Promise.reject(foo.bar ??= 5)", - "Promise.reject(foo[bar] ??= 5)" + "Promise.reject(foo[bar] ??= 5)", + + // Private fields + "class C { #reject; foo() { Promise.#reject(5); } }", + "class C { #error; foo() { Promise.reject(this.#error); } }" ], invalid: [ diff --git a/tests/lib/rules/prefer-regex-literals.js b/tests/lib/rules/prefer-regex-literals.js index 0ddaa8dae3d..ccd88ae74fd 100644 --- a/tests/lib/rules/prefer-regex-literals.js +++ b/tests/lib/rules/prefer-regex-literals.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("prefer-regex-literals", rule, { valid: [ @@ -124,6 +124,10 @@ ruleTester.run("prefer-regex-literals", rule, { { code: "new globalThis.RegExp('a');", env: { es2017: true } + }, + { + code: "class C { #RegExp; foo() { globalThis.#RegExp('a'); } }", + env: { es2020: true } } ], diff --git a/tests/lib/rules/prefer-spread.js b/tests/lib/rules/prefer-spread.js index 7f48d845f1b..99c8c5fa41a 100644 --- a/tests/lib/rules/prefer-spread.js +++ b/tests/lib/rules/prefer-spread.js @@ -18,7 +18,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); const errors = [{ messageId: "preferSpread", type: "CallExpression" }]; -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("prefer-spread", rule, { valid: [ @@ -43,7 +43,10 @@ ruleTester.run("prefer-spread", rule, { // Optional chaining "(a?.b).c.foo.apply(a?.b.c, args);", - "a?.b.c.foo.apply((a?.b).c, args);" + "a?.b.c.foo.apply((a?.b).c, args);", + + // Private fields + "class C { #apply; foo() { foo.#apply(undefined, args); } }" ], invalid: [ { @@ -115,6 +118,12 @@ ruleTester.run("prefer-spread", rule, { { code: "(a?.b).c.foo.apply((a?.b).c, args);", errors + }, + + // Private fields + { + code: "class C { #foo; foo() { obj.#foo.apply(obj, args); } }", + errors } ] }); diff --git a/tests/lib/rules/quotes.js b/tests/lib/rules/quotes.js index a7e223aab9b..04216d209e6 100644 --- a/tests/lib/rules/quotes.js +++ b/tests/lib/rules/quotes.js @@ -38,6 +38,8 @@ ruleTester.run("quotes", rule, { { code: "var foo = \"a string containing `backtick` quotes\";", options: ["backtick", { avoidEscape: true }] }, { code: "var foo =
;", options: ["backtick"], parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }, { code: "var foo =
Hello world
;", options: ["backtick"], parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }, + { code: "class C { \"f\"; \"m\"() {} }", options: ["double"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { 'f'; 'm'() {} }", options: ["single"], parserOptions: { ecmaVersion: 2022 } }, // Backticks are only okay if they have substitutions, contain a line break, or are tagged { code: "var foo = `back\ntick`;", options: ["single"], parserOptions: { ecmaVersion: 6 } }, @@ -74,7 +76,8 @@ ruleTester.run("quotes", rule, { // `backtick` should not warn property/method names (not computed). { code: "var obj = {\"key0\": 0, 'key1': 1};", options: ["backtick"], parserOptions: { ecmaVersion: 6 } }, { code: "class Foo { 'bar'(){} }", options: ["backtick"], parserOptions: { ecmaVersion: 6 } }, - { code: "class Foo { static ''(){} }", options: ["backtick"], parserOptions: { ecmaVersion: 6 } } + { code: "class Foo { static ''(){} }", options: ["backtick"], parserOptions: { ecmaVersion: 6 } }, + { code: "class C { \"double\"; 'single'; }", options: ["backtick"], parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { @@ -627,6 +630,87 @@ ruleTester.run("quotes", rule, { type: "Literal" } ] + }, + + + // class members + { + code: "class C { 'foo'; }", + output: "class C { \"foo\"; }", + options: ["double"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "Literal" + } + ] + }, + { + code: "class C { 'foo'() {} }", + output: "class C { \"foo\"() {} }", + options: ["double"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "Literal" + } + ] + }, + { + code: "class C { \"foo\"; }", + output: "class C { 'foo'; }", + options: ["single"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "singlequote" }, + type: "Literal" + } + ] + }, + { + code: "class C { \"foo\"() {} }", + output: "class C { 'foo'() {} }", + options: ["single"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "singlequote" }, + type: "Literal" + } + ] + }, + { + code: "class C { [\"foo\"]; }", + output: "class C { [`foo`]; }", + options: ["backtick"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "backtick" }, + type: "Literal" + } + ] + }, + { + code: "class C { foo = \"foo\"; }", + output: "class C { foo = `foo`; }", + options: ["backtick"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "backtick" }, + type: "Literal" + } + ] } ] }); diff --git a/tests/lib/rules/radix.js b/tests/lib/rules/radix.js index ddb8363c1d9..ff933f34f84 100644 --- a/tests/lib/rules/radix.js +++ b/tests/lib/rules/radix.js @@ -44,6 +44,10 @@ ruleTester.run("radix", rule, { "parseInt", "Number.foo();", "Number[parseInt]();", + { code: "class C { #parseInt; foo() { Number.#parseInt(); } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { #parseInt; foo() { Number.#parseInt(foo); } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { #parseInt; foo() { Number.#parseInt(foo, 'bar'); } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { #parseInt; foo() { Number.#parseInt(foo, 10); } }", options: ["as-needed"], parserOptions: { ecmaVersion: 2022 } }, // Ignores if it's shadowed or disabled. "var parseInt; parseInt();", diff --git a/tests/lib/rules/require-atomic-updates.js b/tests/lib/rules/require-atomic-updates.js index bd3738ac979..be6e8f2992a 100644 --- a/tests/lib/rules/require-atomic-updates.js +++ b/tests/lib/rules/require-atomic-updates.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); const VARIABLE_ERROR = { messageId: "nonAtomicUpdate", @@ -36,6 +36,12 @@ const COMPUTED_PROPERTY_ERROR = { type: "AssignmentExpression" }; +const PRIVATE_PROPERTY_ERROR = { + messageId: "nonAtomicUpdate", + data: { value: "foo.#bar" }, + type: "AssignmentExpression" +}; + ruleTester.run("require-atomic-updates", rule, { valid: [ @@ -269,6 +275,10 @@ ruleTester.run("require-atomic-updates", rule, { code: "const foo = []; async function x() { foo[bar].baz += await result; }", errors: [COMPUTED_PROPERTY_ERROR] }, + { + code: "const foo = {}; class C { #bar; async wrap() { foo.#bar += await baz } }", + errors: [PRIVATE_PROPERTY_ERROR] + }, { code: "let foo; async function* x() { foo = (yield foo) + await bar; }", errors: [VARIABLE_ERROR] diff --git a/tests/lib/rules/require-unicode-regexp.js b/tests/lib/rules/require-unicode-regexp.js index 6033d44ca32..16b6be4ff11 100644 --- a/tests/lib/rules/require-unicode-regexp.js +++ b/tests/lib/rules/require-unicode-regexp.js @@ -40,7 +40,8 @@ ruleTester.run("require-unicode-regexp", rule, { { code: "globalThis.RegExp('foo', 'u')", env: { es2020: true } }, { code: "const flags = 'u'; new globalThis.RegExp('', flags)", env: { es2020: true } }, { code: "const flags = 'g'; new globalThis.RegExp('', flags + 'u')", env: { es2020: true } }, - { code: "const flags = 'gimu'; new globalThis.RegExp('foo', flags[3])", env: { es2020: true } } + { code: "const flags = 'gimu'; new globalThis.RegExp('foo', flags[3])", env: { es2020: true } }, + { code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } } ], invalid: [ { diff --git a/tests/lib/rules/semi-spacing.js b/tests/lib/rules/semi-spacing.js index b01dac04b15..c9cccddd5f1 100644 --- a/tests/lib/rules/semi-spacing.js +++ b/tests/lib/rules/semi-spacing.js @@ -57,7 +57,24 @@ ruleTester.run("semi-spacing", rule, { { code: "function foo() { return 2; }", options: [{ after: false }] }, { code: "for ( var i = 0;i < results.length; ) {}", options: [{ after: false }] }, - "do {} while (true); foo" + "do {} while (true); foo", + + // Class fields + { + code: "class C { foo; bar; method() {} }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo }", + parserOptions: { ecmaVersion: 2022 } + }, + + // Empty are ignored (`no-extra-semi` rule will remove those) + "foo; ;;;;;;;;;", + { + code: "class C { foo; ;;;;;;;;;; }", + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { @@ -417,6 +434,55 @@ ruleTester.run("semi-spacing", rule, { endLine: 1, endColumn: 22 }] + }, + + // Class fields + { + code: "class C { foo ;bar;}", + output: "class C { foo; bar;}", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unexpectedWhitespaceBefore", + type: "PropertyDefinition", + line: 1, + column: 14, + endLine: 1, + endColumn: 15 + }, + { + messageId: "missingWhitespaceAfter", + type: "PropertyDefinition", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 + } + ] + }, + { + code: "class C { foo; bar ; }", + output: "class C { foo ;bar ; }", + options: [{ before: true, after: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingWhitespaceBefore", + type: "PropertyDefinition", + line: 1, + column: 14, + endLine: 1, + endColumn: 15 + }, + { + messageId: "unexpectedWhitespaceAfter", + type: "PropertyDefinition", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 + } + ] } ] }); diff --git a/tests/lib/rules/semi-style.js b/tests/lib/rules/semi-style.js index 319a80c9b4b..82d9575de2f 100644 --- a/tests/lib/rules/semi-style.js +++ b/tests/lib/rules/semi-style.js @@ -33,6 +33,8 @@ ruleTester.run("semi-style", rule, { { code: "for(a;b;c);", options: ["last"] }, { code: "for(a;\nb;\nc);", options: ["last"] }, { code: "for((a\n);\n(b\n);\n(c));", options: ["last"] }, + { code: "class C { a; b; }", options: ["last"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\na;\nb;\n}", options: ["last"], parserOptions: { ecmaVersion: 2022 } }, { code: "if(a)foo;\nbar", options: ["last"] }, { code: ";", options: ["first"] }, { code: ";foo;bar;baz;", options: ["first"] }, @@ -40,6 +42,8 @@ ruleTester.run("semi-style", rule, { { code: "for(a;b;c);", options: ["first"] }, { code: "for(a;\nb;\nc);", options: ["first"] }, { code: "for((a\n);\n(b\n);\n(c));", options: ["first"] }, + { code: "class C { a ;b }", options: ["first"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\na\n;b\n}", options: ["first"], parserOptions: { ecmaVersion: 2022 } }, // edge cases { @@ -384,6 +388,32 @@ ruleTester.run("semi-style", rule, { pos: "the beginning of the next line" } }] + }, + + // Class fields + { + code: "class C { foo\n;bar }", + output: "class C { foo;\nbar }", + options: ["last"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "expectedSemiColon", + data: { + pos: "the end of the previous line" + } + }] + }, + { + code: "class C { foo;\nbar }", + output: "class C { foo\n;bar }", + options: ["first"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "expectedSemiColon", + data: { + pos: "the beginning of the next line" + } + }] } ] }); diff --git a/tests/lib/rules/semi.js b/tests/lib/rules/semi.js index ce6310e1be0..740251bffe9 100644 --- a/tests/lib/rules/semi.js +++ b/tests/lib/rules/semi.js @@ -230,6 +230,82 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "never" }], parserOptions: { ecmaVersion: 2015 } + }, + + // Class fields + { + code: "class C { foo; }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = obj\n;[bar] }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo;\n[bar]; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n;[bar] }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n[bar] }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n;[bar] }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n[bar] }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = () => {}\n;[bar] }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = () => {}\n[bar] }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = () => {}\n;[bar] }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = () => {}\n[bar] }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo() {} }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo() {}; }", // no-extra-semi reports it + options: ["never"], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -1197,6 +1273,63 @@ ruleTester.run("semi", rule, { options: ["never", { beforeStatementContinuationChars: "never" }], parserOptions: { ecmaVersion: 2015 }, errors: ["Extra semicolon."] + }, + + // Class fields + { + code: "class C { foo }", + output: "class C { foo; }", + parserOptions: { ecmaVersion: 2022 }, + errors: ["Missing semicolon."] + }, + { + code: "class C { foo }", + output: "class C { foo; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: ["Missing semicolon."] + }, + { + code: "class C { foo; }", + output: "class C { foo }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: ["Extra semicolon."] + }, + { + code: "class C { foo\n[bar]; }", + output: "class C { foo;\n[bar]; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: ["Missing semicolon."] + }, + { + code: "class C { foo\n[bar] }", + output: "class C { foo;\n[bar] }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 }, + errors: ["Missing semicolon."] + }, + { + code: "class C { foo\n;[bar] }", + output: "class C { foo\n[bar] }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 }, + errors: ["Extra semicolon."] + }, + { + code: "class C { foo = () => {}\n[bar] }", + output: "class C { foo = () => {};\n[bar] }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 }, + errors: ["Missing semicolon."] + }, + { + code: "class C { foo = () => {}\n;[bar] }", + output: "class C { foo = () => {}\n[bar] }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 }, + errors: ["Extra semicolon."] } ] }); diff --git a/tests/lib/rules/space-infix-ops.js b/tests/lib/rules/space-infix-ops.js index 2e6f423e919..f6c2130da8f 100644 --- a/tests/lib/rules/space-infix-ops.js +++ b/tests/lib/rules/space-infix-ops.js @@ -49,9 +49,17 @@ ruleTester.run("space-infix-ops", rule, { // TypeScript Type Aliases { code: "type Foo = T;", parser: parser("typescript-parsers/type-alias"), parserOptions: { ecmaVersion: 6 } }, + // Logical Assignments { code: "a &&= b", parserOptions: { ecmaVersion: 2021 } }, { code: "a ||= b", parserOptions: { ecmaVersion: 2021 } }, - { code: "a ??= b", parserOptions: { ecmaVersion: 2021 } } + { code: "a ??= b", parserOptions: { ecmaVersion: 2021 } }, + + // Class Fields + { code: "class C { a; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { a = b; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { 'a' = b; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { [a] = b; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { #a = b; }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { @@ -492,6 +500,7 @@ ruleTester.run("space-infix-ops", rule, { }] }, + // Logical Assignments { code: "a&&=b", output: "a &&= b", @@ -533,6 +542,36 @@ ruleTester.run("space-infix-ops", rule, { endColumn: 5, type: "AssignmentExpression" }] + }, + + // Class Fields + { + code: "class C { a=b; }", + output: "class C { a = b; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSpace", + data: { operator: "=" }, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + type: "PropertyDefinition" + }] + }, + { + code: "class C { [a ]= b; }", + output: "class C { [a ] = b; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSpace", + data: { operator: "=" }, + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + type: "PropertyDefinition" + }] } ] }); diff --git a/tests/lib/rules/strict.js b/tests/lib/rules/strict.js index 92abffda16c..fc74bc994fa 100644 --- a/tests/lib/rules/strict.js +++ b/tests/lib/rules/strict.js @@ -404,7 +404,20 @@ ruleTester.run("strict", rule, { parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unnecessaryInClasses", type: "ExpressionStatement" }] }, - + { + code: "class A { field = () => { \"use strict\"; } }", + output: "class A { field = () => { } }", + options: ["function"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unnecessaryInClasses", type: "ExpressionStatement" }] + }, + { + code: "class A { field = function() { \"use strict\"; } }", + output: "class A { field = function() { } }", + options: ["function"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unnecessaryInClasses", type: "ExpressionStatement" }] + }, // "safe" mode corresponds to "global" if ecmaFeatures.globalReturn is true, otherwise "function" { diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js index d0ad2748431..f69fbfaceaa 100644 --- a/tests/lib/rules/utils/ast-utils.js +++ b/tests/lib/rules/utils/ast-utils.js @@ -878,7 +878,17 @@ describe("ast-utils", () => { "class A { static *foo() {} }": "static generator method 'foo'", "class A { static async foo() {} }": "static async method 'foo'", "class A { static get foo() {} }": "static getter 'foo'", - "class A { static set foo(a) {} }": "static setter 'foo'" + "class A { static set foo(a) {} }": "static setter 'foo'", + "class A { foo = () => {}; }": "method 'foo'", + "class A { foo = function() {}; }": "method 'foo'", + "class A { foo = function bar() {}; }": "method 'foo'", + "class A { static foo = () => {}; }": "static method 'foo'", + "class A { '#foo' = () => {}; }": "method '#foo'", + "class A { #foo = () => {}; }": "private method #foo", + "class A { static #foo = () => {}; }": "static private method #foo", + "class A { '#foo'() {} }": "method '#foo'", + "class A { #foo() {} }": "private method #foo", + "class A { static #foo() {} }": "static private method #foo" }; Object.keys(expectedResults).forEach(key => { @@ -892,7 +902,7 @@ describe("ast-utils", () => { }) }))); - linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 8 } }); + linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 13 } }); }); }); }); @@ -940,7 +950,12 @@ describe("ast-utils", () => { "class A { static *foo() {} }": [10, 21], "class A { static async foo() {} }": [10, 26], "class A { static get foo() {} }": [10, 24], - "class A { static set foo(a) {} }": [10, 24] + "class A { static set foo(a) {} }": [10, 24], + "class A { foo = function() {}; }": [10, 24], + "class A { foo = function bar() {}; }": [10, 28], + "class A { static foo = function() {}; }": [10, 31], + "class A { foo = () => {}; }": [10, 16], + "class A { foo = arg => {}; }": [10, 16] }; Object.keys(expectedResults).forEach(key => { @@ -965,7 +980,7 @@ describe("ast-utils", () => { }) }))); - linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 8 } }, "test.js", true); + linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 13 } }, "test.js", true); }); }); });