diff --git a/CHANGELOG.md b/CHANGELOG.md index 6817d8ea..0c1ae914 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v3.0.3 (2021-05-10) + +* Docs: rm global-installed usage ([#116](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/issues/116)) ([1f99c7c](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/commit/1f99c7ce827f576ffba8a76fc8d2bee534648f8a)) +* Docs: update CI badge for github actions in README ([#115](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/issues/115)) ([ccac2c2](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/commit/ccac2c2e8b21b18f46f2f409ce9c66f302bbee19)) +* Docs: add npm badge to README ([#114](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/issues/114)) ([36d16df](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/commit/36d16dfc1d408dfcb3ddd0dfbf02007d1c1fb98c)) +* Fix: Improve detection of static `description` strings and ignore non-static descriptions in `require-meta-docs-description` rule ([#113](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/issues/113)) ([1840a53](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/commit/1840a53d98fd602feae20219d37510ecbe30fd74)) +* Chore: refactor `utils.getRuleInfo` ([#112](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/issues/112)) ([98e893b](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/commit/98e893b941ab1e41931a17d141723547a0cad659)) + ## v3.0.2 (2021-04-16) * Fix: `require-meta-schema`: Fix false positive ([#111](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/issues/111)) ([9f4f461](git@github.com:not-an-aardvark/eslint-plugin-eslint-plugin/commit/9f4f461969b0f89d40219198423b39eea7b63d1e)) diff --git a/README.md b/README.md index 1e4ae467..34bf6976 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# eslint-plugin-eslint-plugin [![Build Status](https://travis-ci.org/not-an-aardvark/eslint-plugin-eslint-plugin.svg?branch=master)](https://travis-ci.org/not-an-aardvark/eslint-plugin-eslint-plugin) +# eslint-plugin-eslint-plugin ![CI](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/workflows/CI/badge.svg) [![NPM version](https://img.shields.io/npm/v/eslint-plugin-eslint-plugin.svg?style=flat)](https://npmjs.org/package/eslint-plugin-eslint-plugin) An ESLint plugin for linting ESLint plugins @@ -16,8 +16,6 @@ Next, install `eslint-plugin-eslint-plugin`: $ npm install eslint-plugin-eslint-plugin --save-dev ``` -**Note:** If you installed ESLint globally (using the `-g` flag) then you must also install `eslint-plugin-eslint-plugin` globally. - ## Usage Add `eslint-plugin` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: diff --git a/lib/rules/meta-property-ordering.js b/lib/rules/meta-property-ordering.js index 6fb67a59..6a58ff48 100644 --- a/lib/rules/meta-property-ordering.js +++ b/lib/rules/meta-property-ordering.js @@ -27,7 +27,7 @@ module.exports = { create (context) { const sourceCode = context.getSourceCode(); - const info = getRuleInfo(sourceCode.ast); + const info = getRuleInfo(sourceCode); const message = 'The meta properties should be placed in a consistent order: [{{order}}].'; const order = context.options[0] || ['type', 'docs', 'fixable', 'schema', 'messages']; diff --git a/lib/rules/prefer-object-rule.js b/lib/rules/prefer-object-rule.js index 4cd781a6..f8d3b499 100644 --- a/lib/rules/prefer-object-rule.js +++ b/lib/rules/prefer-object-rule.js @@ -31,7 +31,7 @@ module.exports = { // ---------------------------------------------------------------------- const sourceCode = context.getSourceCode(); - const ruleInfo = utils.getRuleInfo(sourceCode.ast); + const ruleInfo = utils.getRuleInfo(sourceCode); return { Program () { diff --git a/lib/rules/report-message-format.js b/lib/rules/report-message-format.js index d2291ea1..cef98cfb 100644 --- a/lib/rules/report-message-format.js +++ b/lib/rules/report-message-format.js @@ -54,7 +54,7 @@ module.exports = { return { Program (node) { contextIdentifiers = utils.getContextIdentifiers(context, node); - const ruleInfo = utils.getRuleInfo(context.getSourceCode().ast); + const ruleInfo = utils.getRuleInfo(context.getSourceCode()); const messagesObject = ruleInfo && ruleInfo.meta && ruleInfo.meta.type === 'ObjectExpression' && diff --git a/lib/rules/require-meta-docs-description.js b/lib/rules/require-meta-docs-description.js index dbf7e329..ce85c672 100644 --- a/lib/rules/require-meta-docs-description.js +++ b/lib/rules/require-meta-docs-description.js @@ -1,5 +1,6 @@ 'use strict'; +const { getStaticValue } = require('eslint-utils'); const utils = require('../utils'); // ------------------------------------------------------------------------------ @@ -8,16 +9,6 @@ const utils = require('../utils'); const DEFAULT_PATTERN = new RegExp('^(enforce|require|disallow)'); -/** - * Whether or not the node is a string literal - * - * @param {object} node - * @returns {boolean} whether or not the node is a string literal - */ -function isStringLiteral (node) { - return node.type === 'Literal' && typeof node.value === 'string'; -} - module.exports = { meta: { docs: { @@ -47,7 +38,7 @@ module.exports = { create (context) { const sourceCode = context.getSourceCode(); - const info = utils.getRuleInfo(sourceCode.ast, sourceCode.scopeManager); + const info = utils.getRuleInfo(sourceCode); return { Program () { @@ -70,11 +61,20 @@ module.exports = { if (!descriptionNode) { context.report({ node: docsNode ? docsNode : metaNode, messageId: 'missing' }); - } else if (!isStringLiteral(descriptionNode.value) || descriptionNode.value.value === '') { + return; + } + + const staticValue = getStaticValue(descriptionNode.value, context.getScope()); + if (!staticValue) { + // Ignore non-static values since we can't determine what they look like. + return; + } + + if (typeof staticValue.value !== 'string' || staticValue.value === '') { context.report({ node: descriptionNode.value, messageId: 'wrongType' }); - } else if (descriptionNode.value.value !== descriptionNode.value.value.trim()) { + } else if (staticValue.value !== staticValue.value.trim()) { context.report({ node: descriptionNode.value, messageId: 'extraWhitespace' }); - } else if (!pattern.test(descriptionNode.value.value)) { + } else if (!pattern.test(staticValue.value)) { context.report({ node: descriptionNode.value, message: '`meta.docs.description` must match the regexp {{pattern}}.', diff --git a/lib/rules/require-meta-docs-url.js b/lib/rules/require-meta-docs-url.js index da211043..78431398 100644 --- a/lib/rules/require-meta-docs-url.js +++ b/lib/rules/require-meta-docs-url.js @@ -66,7 +66,7 @@ module.exports = { return { Program (node) { - const info = util.getRuleInfo(node); + const info = util.getRuleInfo(sourceCode); if (info === null) { return; } diff --git a/lib/rules/require-meta-fixable.js b/lib/rules/require-meta-fixable.js index 497e88f5..bdf368b2 100644 --- a/lib/rules/require-meta-fixable.js +++ b/lib/rules/require-meta-fixable.js @@ -24,7 +24,7 @@ module.exports = { create (context) { const sourceCode = context.getSourceCode(); - const ruleInfo = utils.getRuleInfo(sourceCode.ast); + const ruleInfo = utils.getRuleInfo(sourceCode); let contextIdentifiers; let usesFixFunctions; diff --git a/lib/rules/require-meta-schema.js b/lib/rules/require-meta-schema.js index f46b0392..143563c8 100644 --- a/lib/rules/require-meta-schema.js +++ b/lib/rules/require-meta-schema.js @@ -35,8 +35,8 @@ module.exports = { create (context) { const sourceCode = context.getSourceCode(); - const { ast, scopeManager } = sourceCode; - const info = utils.getRuleInfo(ast, scopeManager); + const { scopeManager } = sourceCode; + const info = utils.getRuleInfo(sourceCode); return { Program () { diff --git a/lib/rules/require-meta-type.js b/lib/rules/require-meta-type.js index d9cb5206..f0bb1e86 100644 --- a/lib/rules/require-meta-type.js +++ b/lib/rules/require-meta-type.js @@ -30,7 +30,7 @@ module.exports = { create (context) { const sourceCode = context.getSourceCode(); - const info = utils.getRuleInfo(sourceCode.ast, sourceCode.scopeManager); + const info = utils.getRuleInfo(sourceCode); // ---------------------------------------------------------------------- // Helpers diff --git a/lib/utils.js b/lib/utils.js index fd92b1b5..829c16ad 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -85,13 +85,13 @@ module.exports = { /** * Performs static analysis on an AST to try to determine the final value of `module.exports`. - * @param {ASTNode} ast The `Program` AST node + * @param {{ast: ASTNode, scopeManager?: ScopeManager}} sourceCode The object contains `Program` AST node, and optional `scopeManager` * @returns {Object} An object with keys `meta`, `create`, and `isNewStyle`. `meta` and `create` correspond to the AST nodes for the final values of `module.exports.meta` and `module.exports.create`. `isNewStyle` will be `true` if `module.exports` is an object, and `false` if module.exports is just the `create` function. If no valid ESLint rule info can be extracted from the file, the return value will be `null`. */ - getRuleInfo (ast, scopeManager) { + getRuleInfo ({ ast, scopeManager }) { const INTERESTING_KEYS = new Set(['create', 'meta']); let exportsVarOverridden = false; let exportsIsFunction = false; @@ -171,7 +171,7 @@ module.exports = { * @returns {Set} A Set of all `Identifier` nodes that are references to the `context` value for the file */ getContextIdentifiers (context, ast) { - const ruleInfo = module.exports.getRuleInfo(ast); + const ruleInfo = module.exports.getRuleInfo({ ast }); if (!ruleInfo || !ruleInfo.create.params.length || ruleInfo.create.params[0].type !== 'Identifier') { return new Set(); diff --git a/package.json b/package.json index a3cce99f..5a80300d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-eslint-plugin", - "version": "3.0.2", + "version": "3.0.3", "description": "An ESLint plugin for linting ESLint plugins", "author": "Teddy Katz", "main": "lib/index.js", diff --git a/tests/lib/rules/require-meta-docs-description.js b/tests/lib/rules/require-meta-docs-description.js index bb5ef8f5..9a0e1dcd 100644 --- a/tests/lib/rules/require-meta-docs-description.js +++ b/tests/lib/rules/require-meta-docs-description.js @@ -32,6 +32,44 @@ ruleTester.run('require-meta-docs-description', rule, { create(context) {} }; `, + ` + module.exports = { + meta: { docs: { description: generateRestOfDescription() } }, + create(context) {} + }; + `, + ` + module.exports = { + meta: { docs: { description: \`enforce with template literal\` } }, + create(context) {} + }; + `, + ` + module.exports = { + meta: { docs: { description: "enforce" + " " + "something" } }, + create(context) {} + }; + `, + ` + module.exports = { + meta: { docs: { description: "enforce " + generateSomething() } }, + create(context) {} + }; + `, + ` + const DESCRIPTION = 'require foo'; + module.exports = { + meta: { docs: { description: DESCRIPTION } }, + create(context) {} + }; + `, + ` + const DESCRIPTION = generateDescription(); + module.exports = { + meta: { docs: { description: DESCRIPTION } }, + create(context) {} + }; + `, { code: ` @@ -88,17 +126,18 @@ ruleTester.run('require-meta-docs-description', rule, { { code: ` module.exports = { - meta: { docs: { description: \`enforce with template literal\` } }, + meta: { docs: { description: '' } }, create(context) {} }; `, output: null, - errors: [{ messageId: 'wrongType', type: 'TemplateLiteral' }], + errors: [{ messageId: 'wrongType', type: 'Literal' }], }, { code: ` + const DESCRIPTION = true; module.exports = { - meta: { docs: { description: SOME_DESCRIPTION } }, + meta: { docs: { description: DESCRIPTION } }, create(context) {} }; `, @@ -107,13 +146,14 @@ ruleTester.run('require-meta-docs-description', rule, { }, { code: ` + const DESCRIPTION = 123; module.exports = { - meta: { docs: { description: '' } }, + meta: { docs: { description: DESCRIPTION } }, create(context) {} }; `, output: null, - errors: [{ messageId: 'wrongType', type: 'Literal' }], + errors: [{ messageId: 'wrongType', type: 'Identifier' }], }, { code: ` @@ -135,6 +175,16 @@ ruleTester.run('require-meta-docs-description', rule, { output: null, errors: [{ message: '`meta.docs.description` must match the regexp /^(enforce|require|disallow)/.', type: 'Literal' }], }, + { + code: ` + module.exports = { + meta: { docs: { description: 'foo' + ' ' + 'bar' } }, + create(context) {} + }; + `, + output: null, + errors: [{ message: '`meta.docs.description` must match the regexp /^(enforce|require|disallow)/.', type: 'BinaryExpression' }], + }, { code: ` module.exports = { diff --git a/tests/lib/utils.js b/tests/lib/utils.js index efbcc970..8f5cb5e4 100644 --- a/tests/lib/utils.js +++ b/tests/lib/utils.js @@ -28,7 +28,7 @@ describe('utils', () => { ].forEach(noRuleCase => { it(`returns null for ${noRuleCase}`, () => { const ast = espree.parse(noRuleCase, { ecmaVersion: 8, range: true }); - assert.isNull(utils.getRuleInfo(ast), 'Expected no rule to be found'); + assert.isNull(utils.getRuleInfo({ ast }), 'Expected no rule to be found'); }); }); }); @@ -115,7 +115,7 @@ describe('utils', () => { Object.keys(CASES).forEach(ruleSource => { it(ruleSource, () => { const ast = espree.parse(ruleSource, { ecmaVersion: 6, range: true }); - const ruleInfo = utils.getRuleInfo(ast); + const ruleInfo = utils.getRuleInfo({ ast }); assert( lodash.isMatch(ruleInfo, CASES[ruleSource]), `Expected \n${util.inspect(ruleInfo)}\nto match\n${util.inspect(CASES[ruleSource])}` @@ -139,8 +139,8 @@ describe('utils', () => { isNewStyle: true, }; it(`ScopeOptions: ${JSON.stringify(scopeOptions)}`, () => { - const scope = eslintScope.analyze(ast, scopeOptions); - const ruleInfo = utils.getRuleInfo(ast, scope); + const scopeManager = eslintScope.analyze(ast, scopeOptions); + const ruleInfo = utils.getRuleInfo({ ast, scopeManager }); assert( lodash.isMatch(ruleInfo, expected), `Expected \n${util.inspect(ruleInfo)}\nto match\n${util.inspect(expected)}`