diff --git a/docs/rules/no-underscore-dangle.md b/docs/rules/no-underscore-dangle.md index e112c7a1b30..ebe7fd7b6d0 100644 --- a/docs/rules/no-underscore-dangle.md +++ b/docs/rules/no-underscore-dangle.md @@ -42,6 +42,7 @@ This rule has an object option: * `"allow"` allows specified identifiers to have dangling underscores * `"allowAfterThis": false` (default) disallows dangling underscores in members of the `this` object * `"allowAfterSuper": false` (default) disallows dangling underscores in members of the `super` object +* `"allowAfterThisConstructor": false` (default) disallows dangling underscores in members of the `this.constructor` object * `"enforceInMethodNames": false` (default) allows dangling underscores in method names ### allow @@ -77,6 +78,17 @@ var a = super.foo_; super._bar(); ``` +### allowAfterThisConstructor + +Examples of **correct** code for this rule with the `{ "allowAfterThisConstructor": true }` option: + +```js +/*eslint no-underscore-dangle: ["error", { "allowAfterThisConstructor": true }]*/ + +var a = this.constructor.foo_; +this.constructor._bar(); +``` + ### enforceInMethodNames Examples of **incorrect** code for this rule with the `{ "enforceInMethodNames": true }` option: diff --git a/lib/rules/no-underscore-dangle.js b/lib/rules/no-underscore-dangle.js index 3f59815b575..e910e2739a7 100644 --- a/lib/rules/no-underscore-dangle.js +++ b/lib/rules/no-underscore-dangle.js @@ -38,6 +38,10 @@ module.exports = { type: "boolean", default: false }, + allowAfterThisConstructor: { + type: "boolean", + default: false + }, enforceInMethodNames: { type: "boolean", default: false @@ -54,6 +58,7 @@ module.exports = { const ALLOWED_VARIABLES = options.allow ? options.allow : []; const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false; const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false; + const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false; const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false; //------------------------------------------------------------------------- @@ -72,7 +77,7 @@ module.exports = { /** * Check if identifier has a underscore at the end - * @param {ASTNode} identifier node to evaluate + * @param {string} identifier name of the node * @returns {boolean} true if its is present * @private */ @@ -84,7 +89,7 @@ module.exports = { /** * Check if identifier is a special case member expression - * @param {ASTNode} identifier node to evaluate + * @param {string} identifier name of the node * @returns {boolean} true if its is a special case * @private */ @@ -94,7 +99,7 @@ module.exports = { /** * Check if identifier is a special case variable expression - * @param {ASTNode} identifier node to evaluate + * @param {string} identifier name of the node * @returns {boolean} true if its is a special case * @private */ @@ -104,6 +109,18 @@ module.exports = { return identifier === "_"; } + /** + * Check if a node is a member reference of this.constructor + * @param {ASTNode} node node to evaluate + * @returns {boolean} true if it is a reference on this.constructor + * @private + */ + function isThisConstructorReference(node) { + return node.object.type === "MemberExpression" && + node.object.property.name === "constructor" && + node.object.object.type === "ThisExpression"; + } + /** * Check if function has a underscore at the end * @param {ASTNode} node node to evaluate @@ -156,11 +173,13 @@ module.exports = { function checkForTrailingUnderscoreInMemberExpression(node) { const identifier = node.property.name, isMemberOfThis = node.object.type === "ThisExpression", - isMemberOfSuper = node.object.type === "Super"; + isMemberOfSuper = node.object.type === "Super", + isMemberOfThisConstructor = isThisConstructorReference(node); if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && !(isMemberOfThis && allowAfterThis) && !(isMemberOfSuper && allowAfterSuper) && + !(isMemberOfThisConstructor && allowAfterThisConstructor) && !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) { context.report({ node, diff --git a/tests/lib/rules/no-underscore-dangle.js b/tests/lib/rules/no-underscore-dangle.js index 277c6c85cb5..b3774dc18a6 100644 --- a/tests/lib/rules/no-underscore-dangle.js +++ b/tests/lib/rules/no-underscore-dangle.js @@ -38,7 +38,8 @@ ruleTester.run("no-underscore-dangle", rule, { { code: "const o = { _onClick() { } }", parserOptions: { ecmaVersion: 6 } }, { code: "const o = { onClick_() { } }", parserOptions: { ecmaVersion: 6 } }, { code: "const o = { _foo: 'bar' }", parserOptions: { ecmaVersion: 6 } }, - { code: "const o = { foo_: 'bar' }", parserOptions: { ecmaVersion: 6 } } + { code: "const o = { foo_: 'bar' }", parserOptions: { ecmaVersion: 6 } }, + { code: "this.constructor._bar", options: [{ allowAfterThisConstructor: true }] } ], invalid: [ { code: "var _foo = 1", errors: [{ message: "Unexpected dangling '_' in '_foo'.", type: "VariableDeclarator" }] }, @@ -53,6 +54,8 @@ ruleTester.run("no-underscore-dangle", rule, { { code: "class foo { _onClick() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in '_onClick'.", type: "MethodDefinition" }] }, { code: "class foo { onClick_() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in 'onClick_'.", type: "MethodDefinition" }] }, { code: "const o = { _onClick() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in '_onClick'.", type: "Property" }] }, - { code: "const o = { onClick_() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in 'onClick_'.", type: "Property" }] } + { code: "const o = { onClick_() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected dangling '_' in 'onClick_'.", type: "Property" }] }, + { code: "this.constructor._bar", errors: [{ message: "Unexpected dangling '_' in '_bar'.", type: "MemberExpression" }] }, + { code: "foo.constructor._bar", options: [{ allowAfterThisConstructor: true }], errors: [{ message: "Unexpected dangling '_' in '_bar'.", type: "MemberExpression" }] } ] });