Skip to content

Commit f6c4948

Browse files
authored
fix: Avoid false positives for external meta docs in require-meta-* rules (#639)
1 parent 92e07da commit f6c4948

9 files changed

Lines changed: 345 additions & 14 deletions

lib/rules/require-meta-docs-description.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,16 @@ const rule: Rule.RuleModule = {
5252
const { scopeManager } = sourceCode;
5353

5454
const {
55+
docsMayHaveUnknownProperties,
5556
docsNode,
5657
metaNode,
5758
metaPropertyNode: descriptionNode,
5859
} = getMetaDocsProperty('description', ruleInfo, scopeManager);
5960

6061
if (!descriptionNode) {
62+
if (docsMayHaveUnknownProperties) {
63+
return;
64+
}
6165
context.report({
6266
node: docsNode || metaNode || ruleInfo.create,
6367
messageId: 'missing',

lib/rules/require-meta-docs-recommended.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,16 @@ const rule: Rule.RuleModule = {
7474

7575
const { scopeManager } = sourceCode;
7676
const {
77+
docsMayHaveUnknownProperties,
7778
docsNode,
7879
metaNode,
7980
metaPropertyNode: descriptionNode,
8081
} = getMetaDocsProperty('recommended', ruleInfo, scopeManager);
8182

8283
if (!descriptionNode) {
84+
if (docsMayHaveUnknownProperties) {
85+
return {};
86+
}
8387
const docNodeValue = docsNode?.value;
8488
const suggestions: Rule.SuggestionReportDescriptor[] =
8589
docNodeValue?.type === 'ObjectExpression'

lib/rules/require-meta-docs-url.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ const rule: Rule.RuleModule = {
8989
const { scopeManager } = sourceCode;
9090

9191
const {
92+
docsMayHaveUnknownProperties,
9293
docsNode,
9394
metaNode,
9495
metaPropertyNode: urlPropNode,
@@ -102,6 +103,10 @@ const rule: Rule.RuleModule = {
102103
return;
103104
}
104105

106+
if (!urlPropNode && docsMayHaveUnknownProperties) {
107+
return;
108+
}
109+
105110
if (
106111
staticValue &&
107112
typeof staticValue.value === 'string' &&

lib/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export type ViolationAndSuppressionData = {
7575
};
7676

7777
export type MetaDocsProperty = {
78+
docsMayHaveUnknownProperties: boolean;
7879
docsNode: Property | undefined;
7980
metaNode: Node | undefined;
8081
metaPropertyNode: Property | undefined;

lib/utils.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,9 @@ function resolveSpreadObject(
955955
scopeManager: Scope.ScopeManager,
956956
): SpreadResolution {
957957
let node: Expression | FunctionDeclaration = argument;
958+
if (node.type === 'Identifier' && isUndefinedIdentifier(node)) {
959+
return { kind: 'empty' };
960+
}
958961
if (node.type === 'Identifier') {
959962
const value = findVariableValue(node, scopeManager);
960963
if (!value) {
@@ -1125,14 +1128,31 @@ export function getMetaDocsProperty(
11251128
.filter((node) => node.type === 'Property')
11261129
.find((p) => getKeyName(p) === 'docs');
11271130

1131+
const docsResolution = docsNode
1132+
? resolveSpreadObject(docsNode.value as Expression, scopeManager)
1133+
: undefined;
1134+
const docsObjectNode =
1135+
docsResolution?.kind === 'object' ? docsResolution.node : undefined;
1136+
11281137
const metaPropertyNode = evaluateObjectProperties(
1129-
docsNode?.value,
1138+
docsObjectNode,
11301139
scopeManager,
11311140
)
11321141
.filter((node) => node.type === 'Property')
11331142
.find((p) => getKeyName(p) === propertyName);
11341143

1135-
return { docsNode, metaNode, metaPropertyNode };
1144+
const docsMayHaveUnknownProperties = Boolean(
1145+
docsNode &&
1146+
(docsResolution?.kind === 'unknown' ||
1147+
hasUnresolvedObjectSpread(docsObjectNode, scopeManager)),
1148+
);
1149+
1150+
return {
1151+
docsNode,
1152+
docsMayHaveUnknownProperties,
1153+
metaNode,
1154+
metaPropertyNode,
1155+
};
11361156
}
11371157

11381158
/**

tests/lib/rules/require-meta-docs-description.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,57 @@ ruleTester.run('require-meta-docs-description', rule, {
1717
valid: [
1818
'foo()', // No rule.
1919
'module.exports = {};', // No rule.
20+
`
21+
const docs = require('./rule.docs.js');
22+
module.exports = {
23+
meta: { docs },
24+
create(context) {}
25+
};
26+
`,
27+
`
28+
const docs = require('./rule.docs.js');
29+
module.exports = {
30+
meta: { docs: { ...docs } },
31+
create(context) {}
32+
};
33+
`,
34+
{
35+
code: `
36+
import docs from './rule.docs.js';
37+
export default {
38+
meta: { docs },
39+
create(context) {}
40+
};
41+
`,
42+
languageOptions: { sourceType: 'module' },
43+
},
44+
`
45+
const baseRule = require('./base-rule.js');
46+
module.exports = {
47+
meta: { docs: baseRule.meta.docs },
48+
create(context) {}
49+
};
50+
`,
51+
`
52+
const baseRule = require('./base-rule.js');
53+
module.exports = {
54+
meta: { docs: { ...baseRule.meta.docs } },
55+
create(context) {}
56+
};
57+
`,
58+
`
59+
module.exports = {
60+
meta: { docs: getDocs() },
61+
create(context) {}
62+
};
63+
`,
64+
`
65+
const docs = { description: 'disallow foo' };
66+
module.exports = {
67+
meta: { docs },
68+
create(context) {}
69+
};
70+
`,
2071
`
2172
module.exports = {
2273
meta: { docs: { description: 'disallow unused variables' } },
@@ -218,6 +269,45 @@ ruleTester.run('require-meta-docs-description', rule, {
218269
},
219270
],
220271
},
272+
{
273+
code: `
274+
module.exports = {
275+
meta: { docs: undefined },
276+
create(context) {}
277+
};
278+
`,
279+
output: null,
280+
errors: [
281+
{
282+
messageId: 'missing',
283+
type: 'Property',
284+
column: 19,
285+
endColumn: 34,
286+
endLine: 3,
287+
line: 3,
288+
},
289+
],
290+
},
291+
{
292+
code: `
293+
const docs = {};
294+
module.exports = {
295+
meta: { docs },
296+
create(context) {}
297+
};
298+
`,
299+
output: null,
300+
errors: [
301+
{
302+
messageId: 'missing',
303+
type: 'Property',
304+
column: 19,
305+
endColumn: 23,
306+
endLine: 4,
307+
line: 4,
308+
},
309+
],
310+
},
221311
{
222312
code: `
223313
module.exports = {

tests/lib/rules/require-meta-docs-recommended.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,50 @@ ruleTester.run('require-meta-docs-recommended', rule, {
1010
valid: [
1111
'foo()',
1212
'module.exports = {};',
13+
`
14+
const docs = require('./rule.docs.js');
15+
module.exports = {
16+
meta: { docs },
17+
create(context) {}
18+
};
19+
`,
20+
`
21+
const docs = require('./rule.docs.js');
22+
module.exports = {
23+
meta: { docs: { ...docs } },
24+
create(context) {}
25+
};
26+
`,
27+
{
28+
code: `
29+
import docs from './rule.docs.js';
30+
export default {
31+
meta: { docs },
32+
create(context) {}
33+
};
34+
`,
35+
languageOptions: { sourceType: 'module' },
36+
},
37+
`
38+
const baseRule = require('./base-rule.js');
39+
module.exports = {
40+
meta: { docs: baseRule.meta.docs },
41+
create(context) {}
42+
};
43+
`,
44+
`
45+
const baseRule = require('./base-rule.js');
46+
module.exports = {
47+
meta: { docs: { ...baseRule.meta.docs } },
48+
create(context) {}
49+
};
50+
`,
51+
`
52+
module.exports = {
53+
meta: { docs: getDocs() },
54+
create(context) {}
55+
};
56+
`,
1357
`
1458
module.exports = {
1559
meta: { docs: { recommended: true } },

0 commit comments

Comments
 (0)