From 0e2b72526572c5b17a24c9fa5b668fa3ccc609ac Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Sat, 10 Jul 2021 12:57:50 -0400 Subject: [PATCH 1/2] Fix: Improve detection of static `url` strings in `require-meta-docs-url` rule * Add static value detection for `url` variables * Ignore when we can't statically determine the value of a given `url` * Improve reporting location and tests for this * Use `messageId` * Add tests --- lib/rules/require-meta-docs-url.js | 65 ++++--- tests/lib/rules/require-meta-docs-url.js | 220 +++++++++++++++++++---- 2 files changed, 220 insertions(+), 65 deletions(-) diff --git a/lib/rules/require-meta-docs-url.js b/lib/rules/require-meta-docs-url.js index 234d80eb..e324d568 100644 --- a/lib/rules/require-meta-docs-url.js +++ b/lib/rules/require-meta-docs-url.js @@ -10,6 +10,7 @@ const path = require('path'); const util = require('../utils'); +const { getStaticValue } = require('eslint-utils'); // ----------------------------------------------------------------------------- // Rule Definition @@ -31,6 +32,11 @@ module.exports = { }, additionalProperties: false, }], + messages: { + mismatch: '`meta.docs.url` property must be `{{expectedUrl}}`.', + missing: 'Rules should export a `meta.docs.url` property.', + wrongType: '`meta.docs.url` property must be a string.', + }, }, /** @@ -48,24 +54,22 @@ module.exports = { : options.pattern.replace(/{{\s*name\s*}}/g, ruleName); /** - * Check whether a given node is the expected URL. - * @param {Node} node The node of property value to check. + * Check whether a given URL is the expected URL. + * @param {String} url The URL to check. * @returns {boolean} `true` if the node is the expected URL. */ - function isExpectedUrl (node) { + function isExpectedUrl (url) { return Boolean( - node && - node.type === 'Literal' && - typeof node.value === 'string' && + typeof url === 'string' && ( expectedUrl === undefined || - node.value === expectedUrl + url === expectedUrl ) ); } return { - Program (node) { + Program () { const info = util.getRuleInfo(sourceCode); if (info === null) { return; @@ -81,40 +85,45 @@ module.exports = { docsPropNode.value.properties && docsPropNode.value.properties.find(p => p.type === 'Property' && util.getKeyName(p) === 'url'); - if (isExpectedUrl(urlPropNode && urlPropNode.value)) { + const staticValue = urlPropNode ? getStaticValue(urlPropNode.value, context.getScope()) : undefined; + if (urlPropNode && !staticValue) { + // Ignore non-static values since we can't determine what they look like. + return; + } + + if (isExpectedUrl(staticValue && staticValue.value)) { return; } context.report({ - loc: - (urlPropNode && urlPropNode.value.loc) || - (docsPropNode && docsPropNode.value.loc) || - (metaNode && metaNode.loc) || - node.loc.start, - - message: - !urlPropNode ? 'Rules should export a `meta.docs.url` property.' : + node: (urlPropNode && urlPropNode.value) || (docsPropNode && docsPropNode.value) || metaNode || info.create, + + messageId: + !urlPropNode ? 'missing' : // eslint-disable-next-line unicorn/no-nested-ternary - !expectedUrl ? '`meta.docs.url` property must be a string.' : - /* otherwise */ '`meta.docs.url` property must be `{{expectedUrl}}`.', + !expectedUrl ? 'wrongType' : + /* otherwise */ 'mismatch', data: { expectedUrl, }, fix (fixer) { - if (expectedUrl) { - const urlString = JSON.stringify(expectedUrl); - if (urlPropNode) { + if (!expectedUrl) { + return null; + } + + const urlString = JSON.stringify(expectedUrl); + if (urlPropNode) { + if (urlPropNode.value.type === 'Literal' || (urlPropNode.value.type === 'Identifier' && urlPropNode.value.name === 'undefined')) { return fixer.replaceText(urlPropNode.value, urlString); } - if (docsPropNode && docsPropNode.value.type === 'ObjectExpression') { - return util.insertProperty(fixer, docsPropNode.value, `url: ${urlString}`, sourceCode); - } - if (!docsPropNode && metaNode && metaNode.type === 'ObjectExpression') { - return util.insertProperty(fixer, metaNode, `docs: {\nurl: ${urlString}\n}`, sourceCode); - } + } else if (docsPropNode && docsPropNode.value.type === 'ObjectExpression') { + return util.insertProperty(fixer, docsPropNode.value, `url: ${urlString}`, sourceCode); + } else if (!docsPropNode && metaNode && metaNode.type === 'ObjectExpression') { + return util.insertProperty(fixer, metaNode, `docs: {\nurl: ${urlString}\n}`, sourceCode); } + return null; }, }); diff --git a/tests/lib/rules/require-meta-docs-url.js b/tests/lib/rules/require-meta-docs-url.js index 6513286e..b7195aef 100644 --- a/tests/lib/rules/require-meta-docs-url.js +++ b/tests/lib/rules/require-meta-docs-url.js @@ -19,7 +19,7 @@ const rule = require('../../../lib/rules/require-meta-docs-url'); const tester = new RuleTester({ parserOptions: { - ecmaVersion: 2018, + ecmaVersion: 2020, }, }); @@ -66,6 +66,46 @@ tester.run('require-meta-docs-url', rule, { pattern: 'path/to/{{name}}.md', }], }, + { + // `url` in variable. + filename: 'test-rule', + code: ` + const url = "path/to/test-rule.md"; + module.exports = { + meta: {docs: {url}}, + create() {} + } + `, + options: [{ + pattern: 'path/to/{{name}}.md', + }], + }, + { + // Can't determine `url` value statically. + filename: 'test-rule', + code: ` + module.exports = { + meta: {docs: {url: foo }}, + create() {} + } + `, + options: [{ + pattern: 'path/to/{{name}}.md', + }], + }, + { + // Can't determine `url` value statically. + filename: 'test-rule', + code: ` + module.exports = { + meta: {docs: {url: getUrl() }}, + create() {} + } + `, + options: [{ + pattern: 'path/to/{{name}}.md', + }], + }, ], invalid: [ @@ -74,7 +114,7 @@ tester.run('require-meta-docs-url', rule, { module.exports = function() {} `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'FunctionExpression' }], }, { code: ` @@ -84,7 +124,7 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'Identifier' }], }, { code: ` @@ -94,7 +134,7 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'Literal' }], }, { code: ` @@ -104,7 +144,7 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -116,7 +156,7 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -128,7 +168,7 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -140,7 +180,7 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'Identifier' }], }, { code: ` @@ -152,7 +192,7 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -166,7 +206,7 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -180,7 +220,7 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -194,7 +234,47 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['`meta.docs.url` property must be a string.'], + errors: [{ messageId: 'wrongType', type: 'Literal' }], + }, + { + // `url` is null + code: ` + module.exports = { + meta: { + docs: { url: null } + }, + create() {} + } + `, + output: null, + errors: [{ messageId: 'wrongType', type: 'Literal' }], + }, + { + // `url` is undefined + code: ` + module.exports = { + meta: { + docs: { url: undefined } + }, + create() {} + } + `, + output: null, + errors: [{ messageId: 'wrongType', type: 'Identifier' }], + }, + { + // `url` in variable. + code: ` + const url = 100; + module.exports = { + meta: { + docs: { url } + }, + create() {} + } + `, + output: null, + errors: [{ messageId: 'wrongType', type: 'Identifier' }], }, { code: ` @@ -208,7 +288,7 @@ tester.run('require-meta-docs-url', rule, { } `, output: null, - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, // ------------------------------------------------------------------------- @@ -222,7 +302,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'FunctionExpression' }], }, { code: ` @@ -235,7 +315,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'Identifier' }], }, { code: ` @@ -248,7 +328,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'Literal' }], }, { code: ` @@ -261,7 +341,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -276,7 +356,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -291,7 +371,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -306,7 +386,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'Identifier' }], }, { code: ` @@ -321,7 +401,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -338,7 +418,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -355,7 +435,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { code: ` @@ -372,7 +452,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['`meta.docs.url` property must be a string.'], + errors: [{ messageId: 'wrongType', type: 'Literal' }], }, { code: ` @@ -389,7 +469,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, // ------------------------------------------------------------------------- @@ -404,7 +484,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'FunctionExpression' }], }, { filename: 'test.js', @@ -418,7 +498,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'Identifier' }], }, { filename: 'test.js', @@ -432,7 +512,7 @@ tester.run('require-meta-docs-url', rule, { options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'Literal' }], }, { filename: 'test.js', @@ -455,7 +535,7 @@ url: "plugin-name/test.md" options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { filename: 'test.js', @@ -481,7 +561,7 @@ url: "plugin-name/test.md" options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { filename: 'test.js', @@ -507,7 +587,7 @@ url: "plugin-name/test.md" options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { filename: 'test.js', @@ -523,7 +603,7 @@ url: "plugin-name/test.md" options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'Identifier' }], }, { filename: 'test.js', @@ -548,7 +628,7 @@ url: "plugin-name/test.md" options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { filename: 'test.js', @@ -576,7 +656,7 @@ url: "plugin-name/test.md" options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { filename: 'test.js', @@ -604,7 +684,7 @@ url: "plugin-name/test.md", options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, { filename: 'test.js', @@ -631,7 +711,73 @@ url: "plugin-name/test.md", options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['`meta.docs.url` property must be `plugin-name/test.md`.'], + errors: [{ message: '`meta.docs.url` property must be `plugin-name/test.md`.', type: 'Literal' }], + }, + { + // `url` in variable, can't autofix it. + filename: 'test.js', + code: ` + const url = 'wrong-url'; + module.exports = { + meta: { + docs: { url } + }, + create() {} + } + `, + output: null, + options: [{ + pattern: 'plugin-name/{{ name }}.md', + }], + errors: [{ message: '`meta.docs.url` property must be `plugin-name/test.md`.', type: 'Identifier' }], + }, + { + // `url` is `null`. + filename: 'test.js', + code: ` + module.exports = { + meta: { + docs: { url: null } + }, + create() {} + } + `, + output: ` + module.exports = { + meta: { + docs: { url: "plugin-name/test.md" } + }, + create() {} + } + `, + options: [{ + pattern: 'plugin-name/{{ name }}.md', + }], + errors: [{ message: '`meta.docs.url` property must be `plugin-name/test.md`.', type: 'Literal' }], + }, + { + // `url` is `undefined`. + filename: 'test.js', + code: ` + module.exports = { + meta: { + docs: { url: undefined } + }, + create() {} + } + `, + output: ` + module.exports = { + meta: { + docs: { url: "plugin-name/test.md" } + }, + create() {} + } + `, + options: [{ + pattern: 'plugin-name/{{ name }}.md', + }], + errors: [{ message: '`meta.docs.url` property must be `plugin-name/test.md`.', type: 'Identifier' }], }, { filename: 'test.js', @@ -659,7 +805,7 @@ url: "plugin-name/test.md" options: [{ pattern: 'plugin-name/{{ name }}.md', }], - errors: ['Rules should export a `meta.docs.url` property.'], + errors: [{ messageId: 'missing', type: 'ObjectExpression' }], }, ], }); From ae07d60bfc9f498366330b00bb7c0426785805b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=9B=E5=AE=9A=E8=B0=94=E7=9A=84=E7=8C=AB?= Date: Mon, 12 Jul 2021 00:16:19 +0800 Subject: [PATCH 2/2] Update lib/rules/require-meta-docs-url.js --- lib/rules/require-meta-docs-url.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/require-meta-docs-url.js b/lib/rules/require-meta-docs-url.js index e324d568..7d09f4f0 100644 --- a/lib/rules/require-meta-docs-url.js +++ b/lib/rules/require-meta-docs-url.js @@ -55,7 +55,7 @@ module.exports = { /** * Check whether a given URL is the expected URL. - * @param {String} url The URL to check. + * @param {string} url The URL to check. * @returns {boolean} `true` if the node is the expected URL. */ function isExpectedUrl (url) {