@sgfe/eslint-plugin-sg 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @sgfe/eslint-plugin-sg might be problematic. Click here for more details.
- package/LICENSE.md +25 -0
- package/README.md +188 -0
- package/configs/all-type-checked.js +10 -0
- package/configs/all.js +11 -0
- package/configs/recommended.js +11 -0
- package/configs/rules-recommended.js +11 -0
- package/configs/rules.js +11 -0
- package/configs/tests-recommended.js +11 -0
- package/configs/tests.js +11 -0
- package/lib/index.js +90 -0
- package/lib/rules/consistent-output.js +70 -0
- package/lib/rules/fixer-return.js +170 -0
- package/lib/rules/meta-property-ordering.js +108 -0
- package/lib/rules/no-deprecated-context-methods.js +98 -0
- package/lib/rules/no-deprecated-report-api.js +83 -0
- package/lib/rules/no-identical-tests.js +87 -0
- package/lib/rules/no-missing-message-ids.js +101 -0
- package/lib/rules/no-missing-placeholders.js +131 -0
- package/lib/rules/no-only-tests.js +99 -0
- package/lib/rules/no-property-in-node.js +86 -0
- package/lib/rules/no-unused-message-ids.js +139 -0
- package/lib/rules/no-unused-placeholders.js +127 -0
- package/lib/rules/no-useless-token-range.js +174 -0
- package/lib/rules/prefer-message-ids.js +109 -0
- package/lib/rules/prefer-object-rule.js +83 -0
- package/lib/rules/prefer-output-null.js +77 -0
- package/lib/rules/prefer-placeholders.js +102 -0
- package/lib/rules/prefer-replace-text.js +91 -0
- package/lib/rules/report-message-format.js +133 -0
- package/lib/rules/require-meta-docs-description.js +110 -0
- package/lib/rules/require-meta-docs-url.js +175 -0
- package/lib/rules/require-meta-fixable.js +137 -0
- package/lib/rules/require-meta-has-suggestions.js +168 -0
- package/lib/rules/require-meta-schema.js +162 -0
- package/lib/rules/require-meta-type.js +77 -0
- package/lib/rules/test-case-property-ordering.js +107 -0
- package/lib/rules/test-case-shorthand-strings.js +124 -0
- package/lib/utils.js +936 -0
- package/package.json +76 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Enforces the order of meta properties
|
3
|
+
*/
|
4
|
+
|
5
|
+
'use strict';
|
6
|
+
|
7
|
+
const { getKeyName, getRuleInfo } = require('../utils');
|
8
|
+
|
9
|
+
// ------------------------------------------------------------------------------
|
10
|
+
// Rule Definition
|
11
|
+
// ------------------------------------------------------------------------------
|
12
|
+
|
13
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
14
|
+
module.exports = {
|
15
|
+
meta: {
|
16
|
+
type: 'suggestion',
|
17
|
+
docs: {
|
18
|
+
description: 'enforce the order of meta properties',
|
19
|
+
category: 'Rules',
|
20
|
+
recommended: false,
|
21
|
+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/meta-property-ordering.md',
|
22
|
+
},
|
23
|
+
fixable: 'code',
|
24
|
+
schema: [
|
25
|
+
{
|
26
|
+
type: 'array',
|
27
|
+
elements: { type: 'string' },
|
28
|
+
},
|
29
|
+
],
|
30
|
+
messages: {
|
31
|
+
inconsistentOrder:
|
32
|
+
'The meta properties should be placed in a consistent order: [{{order}}].',
|
33
|
+
},
|
34
|
+
},
|
35
|
+
|
36
|
+
create(context) {
|
37
|
+
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
|
38
|
+
const ruleInfo = getRuleInfo(sourceCode);
|
39
|
+
if (!ruleInfo) {
|
40
|
+
return {};
|
41
|
+
}
|
42
|
+
|
43
|
+
const order = context.options[0] || [
|
44
|
+
'type',
|
45
|
+
'docs',
|
46
|
+
'fixable',
|
47
|
+
'hasSuggestions',
|
48
|
+
'deprecated',
|
49
|
+
'replacedBy',
|
50
|
+
'schema',
|
51
|
+
'defaultOptions', // https://github.com/eslint/rfcs/tree/main/designs/2023-rule-options-defaults
|
52
|
+
'messages',
|
53
|
+
];
|
54
|
+
|
55
|
+
const orderMap = new Map(order.map((name, i) => [name, i]));
|
56
|
+
|
57
|
+
return {
|
58
|
+
Program() {
|
59
|
+
if (!ruleInfo.meta || ruleInfo.meta.properties.length < 2) {
|
60
|
+
return;
|
61
|
+
}
|
62
|
+
|
63
|
+
const props = ruleInfo.meta.properties;
|
64
|
+
|
65
|
+
let last;
|
66
|
+
|
67
|
+
const violatingProps = props.filter((prop) => {
|
68
|
+
const curr = orderMap.has(getKeyName(prop))
|
69
|
+
? orderMap.get(getKeyName(prop))
|
70
|
+
: Number.POSITIVE_INFINITY;
|
71
|
+
return last > (last = curr);
|
72
|
+
});
|
73
|
+
|
74
|
+
if (violatingProps.length === 0) {
|
75
|
+
return;
|
76
|
+
}
|
77
|
+
|
78
|
+
const knownProps = props
|
79
|
+
.filter((prop) => orderMap.has(getKeyName(prop)))
|
80
|
+
.sort(
|
81
|
+
(a, b) => orderMap.get(getKeyName(a)) - orderMap.get(getKeyName(b))
|
82
|
+
);
|
83
|
+
const unknownProps = props.filter(
|
84
|
+
(prop) => !orderMap.has(getKeyName(prop))
|
85
|
+
);
|
86
|
+
|
87
|
+
for (const violatingProp of violatingProps) {
|
88
|
+
context.report({
|
89
|
+
node: violatingProp,
|
90
|
+
messageId: 'inconsistentOrder',
|
91
|
+
data: {
|
92
|
+
order: knownProps.map(getKeyName).join(', '),
|
93
|
+
},
|
94
|
+
fix(fixer) {
|
95
|
+
const expectedProps = [...knownProps, ...unknownProps];
|
96
|
+
return props.map((prop, k) => {
|
97
|
+
return fixer.replaceText(
|
98
|
+
prop,
|
99
|
+
sourceCode.getText(expectedProps[k])
|
100
|
+
);
|
101
|
+
});
|
102
|
+
},
|
103
|
+
});
|
104
|
+
}
|
105
|
+
},
|
106
|
+
};
|
107
|
+
},
|
108
|
+
};
|
@@ -0,0 +1,98 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Disallows usage of deprecated methods on rule context objects
|
3
|
+
* @author Teddy Katz
|
4
|
+
*/
|
5
|
+
|
6
|
+
'use strict';
|
7
|
+
|
8
|
+
const utils = require('../utils');
|
9
|
+
|
10
|
+
const DEPRECATED_PASSTHROUGHS = {
|
11
|
+
getSource: 'getText',
|
12
|
+
getSourceLines: 'getLines',
|
13
|
+
getAllComments: 'getAllComments',
|
14
|
+
getNodeByRangeIndex: 'getNodeByRangeIndex',
|
15
|
+
getComments: 'getComments',
|
16
|
+
getCommentsBefore: 'getCommentsBefore',
|
17
|
+
getCommentsAfter: 'getCommentsAfter',
|
18
|
+
getCommentsInside: 'getCommentsInside',
|
19
|
+
getJSDocComment: 'getJSDocComment',
|
20
|
+
getFirstToken: 'getFirstToken',
|
21
|
+
getFirstTokens: 'getFirstTokens',
|
22
|
+
getLastToken: 'getLastToken',
|
23
|
+
getLastTokens: 'getLastTokens',
|
24
|
+
getTokenAfter: 'getTokenAfter',
|
25
|
+
getTokenBefore: 'getTokenBefore',
|
26
|
+
getTokenByRangeStart: 'getTokenByRangeStart',
|
27
|
+
getTokens: 'getTokens',
|
28
|
+
getTokensAfter: 'getTokensAfter',
|
29
|
+
getTokensBefore: 'getTokensBefore',
|
30
|
+
getTokensBetween: 'getTokensBetween',
|
31
|
+
};
|
32
|
+
|
33
|
+
// ------------------------------------------------------------------------------
|
34
|
+
// Rule Definition
|
35
|
+
// ------------------------------------------------------------------------------
|
36
|
+
|
37
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
38
|
+
module.exports = {
|
39
|
+
meta: {
|
40
|
+
type: 'suggestion',
|
41
|
+
docs: {
|
42
|
+
description:
|
43
|
+
'disallow usage of deprecated methods on rule context objects',
|
44
|
+
category: 'Rules',
|
45
|
+
recommended: true,
|
46
|
+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-deprecated-context-methods.md',
|
47
|
+
},
|
48
|
+
fixable: 'code',
|
49
|
+
schema: [],
|
50
|
+
messages: {
|
51
|
+
newFormat:
|
52
|
+
'Use `{{contextName}}.getSourceCode().{{replacement}}` instead of `{{contextName}}.{{original}}`.',
|
53
|
+
},
|
54
|
+
},
|
55
|
+
|
56
|
+
create(context) {
|
57
|
+
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
|
58
|
+
|
59
|
+
// ----------------------------------------------------------------------
|
60
|
+
// Public
|
61
|
+
// ----------------------------------------------------------------------
|
62
|
+
|
63
|
+
return {
|
64
|
+
'Program:exit'(ast) {
|
65
|
+
[...utils.getContextIdentifiers(sourceCode.scopeManager, ast)]
|
66
|
+
.filter(
|
67
|
+
(contextId) =>
|
68
|
+
contextId.parent.type === 'MemberExpression' &&
|
69
|
+
contextId === contextId.parent.object &&
|
70
|
+
contextId.parent.property.type === 'Identifier' &&
|
71
|
+
Object.prototype.hasOwnProperty.call(
|
72
|
+
DEPRECATED_PASSTHROUGHS,
|
73
|
+
contextId.parent.property.name
|
74
|
+
)
|
75
|
+
)
|
76
|
+
.forEach((contextId) =>
|
77
|
+
context.report({
|
78
|
+
node: contextId.parent,
|
79
|
+
messageId: 'newFormat',
|
80
|
+
data: {
|
81
|
+
contextName: contextId.name,
|
82
|
+
original: contextId.parent.property.name,
|
83
|
+
replacement:
|
84
|
+
DEPRECATED_PASSTHROUGHS[contextId.parent.property.name],
|
85
|
+
},
|
86
|
+
fix: (fixer) => [
|
87
|
+
fixer.insertTextAfter(contextId, '.getSourceCode()'),
|
88
|
+
fixer.replaceText(
|
89
|
+
contextId.parent.property,
|
90
|
+
DEPRECATED_PASSTHROUGHS[contextId.parent.property.name]
|
91
|
+
),
|
92
|
+
],
|
93
|
+
})
|
94
|
+
);
|
95
|
+
},
|
96
|
+
};
|
97
|
+
},
|
98
|
+
};
|
@@ -0,0 +1,83 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Disallow the version of `context.report()` with multiple arguments
|
3
|
+
* @author Teddy Katz
|
4
|
+
*/
|
5
|
+
|
6
|
+
'use strict';
|
7
|
+
|
8
|
+
const utils = require('../utils');
|
9
|
+
|
10
|
+
// ------------------------------------------------------------------------------
|
11
|
+
// Rule Definition
|
12
|
+
// ------------------------------------------------------------------------------
|
13
|
+
|
14
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
15
|
+
module.exports = {
|
16
|
+
meta: {
|
17
|
+
type: 'suggestion',
|
18
|
+
docs: {
|
19
|
+
description:
|
20
|
+
'disallow the version of `context.report()` with multiple arguments',
|
21
|
+
category: 'Rules',
|
22
|
+
recommended: true,
|
23
|
+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-deprecated-report-api.md',
|
24
|
+
},
|
25
|
+
fixable: 'code', // or "code" or "whitespace"
|
26
|
+
schema: [],
|
27
|
+
messages: {
|
28
|
+
useNewAPI: 'Use the new-style context.report() API.',
|
29
|
+
},
|
30
|
+
},
|
31
|
+
|
32
|
+
create(context) {
|
33
|
+
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
|
34
|
+
let contextIdentifiers;
|
35
|
+
|
36
|
+
// ----------------------------------------------------------------------
|
37
|
+
// Public
|
38
|
+
// ----------------------------------------------------------------------
|
39
|
+
|
40
|
+
return {
|
41
|
+
Program(ast) {
|
42
|
+
contextIdentifiers = utils.getContextIdentifiers(
|
43
|
+
sourceCode.scopeManager,
|
44
|
+
ast
|
45
|
+
);
|
46
|
+
},
|
47
|
+
CallExpression(node) {
|
48
|
+
if (
|
49
|
+
node.callee.type === 'MemberExpression' &&
|
50
|
+
contextIdentifiers.has(node.callee.object) &&
|
51
|
+
node.callee.property.type === 'Identifier' &&
|
52
|
+
node.callee.property.name === 'report' &&
|
53
|
+
(node.arguments.length > 1 ||
|
54
|
+
(node.arguments.length === 1 &&
|
55
|
+
node.arguments[0].type === 'SpreadElement'))
|
56
|
+
) {
|
57
|
+
context.report({
|
58
|
+
node: node.callee.property,
|
59
|
+
messageId: 'useNewAPI',
|
60
|
+
fix(fixer) {
|
61
|
+
const openingParen = sourceCode.getTokenBefore(node.arguments[0]);
|
62
|
+
const closingParen = sourceCode.getLastToken(node);
|
63
|
+
const reportInfo = utils.getReportInfo(node, context);
|
64
|
+
|
65
|
+
if (!reportInfo) {
|
66
|
+
return null;
|
67
|
+
}
|
68
|
+
|
69
|
+
return fixer.replaceTextRange(
|
70
|
+
[openingParen.range[1], closingParen.range[0]],
|
71
|
+
`{${Object.keys(reportInfo)
|
72
|
+
.map(
|
73
|
+
(key) => `${key}: ${sourceCode.getText(reportInfo[key])}`
|
74
|
+
)
|
75
|
+
.join(', ')}}`
|
76
|
+
);
|
77
|
+
},
|
78
|
+
});
|
79
|
+
}
|
80
|
+
},
|
81
|
+
};
|
82
|
+
},
|
83
|
+
};
|
@@ -0,0 +1,87 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview disallow identical tests
|
3
|
+
* @author 薛定谔的猫<hh_2013@foxmail.com>
|
4
|
+
*/
|
5
|
+
|
6
|
+
'use strict';
|
7
|
+
|
8
|
+
const utils = require('../utils');
|
9
|
+
|
10
|
+
// ------------------------------------------------------------------------------
|
11
|
+
// Rule Definition
|
12
|
+
// ------------------------------------------------------------------------------
|
13
|
+
|
14
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
15
|
+
module.exports = {
|
16
|
+
meta: {
|
17
|
+
type: 'problem',
|
18
|
+
docs: {
|
19
|
+
description: 'disallow identical tests',
|
20
|
+
category: 'Tests',
|
21
|
+
recommended: true,
|
22
|
+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-identical-tests.md',
|
23
|
+
},
|
24
|
+
fixable: 'code',
|
25
|
+
schema: [],
|
26
|
+
messages: {
|
27
|
+
identical: 'This test case is identical to another case.',
|
28
|
+
},
|
29
|
+
},
|
30
|
+
|
31
|
+
create(context) {
|
32
|
+
// ----------------------------------------------------------------------
|
33
|
+
// Public
|
34
|
+
// ----------------------------------------------------------------------
|
35
|
+
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
|
36
|
+
|
37
|
+
// ----------------------------------------------------------------------
|
38
|
+
// Helpers
|
39
|
+
// ----------------------------------------------------------------------
|
40
|
+
/**
|
41
|
+
* Create a unique cache key
|
42
|
+
* @param {object} test
|
43
|
+
* @returns {string}
|
44
|
+
*/
|
45
|
+
function toKey(test) {
|
46
|
+
if (test.type !== 'ObjectExpression') {
|
47
|
+
return JSON.stringify([test.type, sourceCode.getText(test)]);
|
48
|
+
}
|
49
|
+
return JSON.stringify([
|
50
|
+
test.type,
|
51
|
+
...test.properties.map((p) => sourceCode.getText(p)).sort(),
|
52
|
+
]);
|
53
|
+
}
|
54
|
+
|
55
|
+
return {
|
56
|
+
Program(ast) {
|
57
|
+
utils.getTestInfo(context, ast).forEach((testRun) => {
|
58
|
+
[testRun.valid, testRun.invalid].forEach((tests) => {
|
59
|
+
const cache = new Set();
|
60
|
+
tests.forEach((test) => {
|
61
|
+
const key = toKey(test);
|
62
|
+
if (cache.has(key)) {
|
63
|
+
context.report({
|
64
|
+
node: test,
|
65
|
+
messageId: 'identical',
|
66
|
+
fix(fixer) {
|
67
|
+
const start = sourceCode.getTokenBefore(test);
|
68
|
+
const end = sourceCode.getTokenAfter(test);
|
69
|
+
return fixer.removeRange(
|
70
|
+
// should remove test's trailing comma
|
71
|
+
[
|
72
|
+
start.range[1],
|
73
|
+
end.value === ',' ? end.range[1] : test.range[1],
|
74
|
+
]
|
75
|
+
);
|
76
|
+
},
|
77
|
+
});
|
78
|
+
} else {
|
79
|
+
cache.add(key);
|
80
|
+
}
|
81
|
+
});
|
82
|
+
});
|
83
|
+
});
|
84
|
+
},
|
85
|
+
};
|
86
|
+
},
|
87
|
+
};
|
@@ -0,0 +1,101 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const utils = require('../utils');
|
4
|
+
|
5
|
+
// ------------------------------------------------------------------------------
|
6
|
+
// Rule Definition
|
7
|
+
// ------------------------------------------------------------------------------
|
8
|
+
|
9
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
10
|
+
module.exports = {
|
11
|
+
meta: {
|
12
|
+
type: 'problem',
|
13
|
+
docs: {
|
14
|
+
description:
|
15
|
+
'disallow `messageId`s that are missing from `meta.messages`',
|
16
|
+
category: 'Rules',
|
17
|
+
recommended: true,
|
18
|
+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-missing-message-ids.md',
|
19
|
+
},
|
20
|
+
fixable: null,
|
21
|
+
schema: [],
|
22
|
+
messages: {
|
23
|
+
missingMessage:
|
24
|
+
'`meta.messages` is missing the messageId "{{messageId}}".',
|
25
|
+
},
|
26
|
+
},
|
27
|
+
|
28
|
+
create(context) {
|
29
|
+
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
|
30
|
+
const { scopeManager } = sourceCode;
|
31
|
+
const ruleInfo = utils.getRuleInfo(sourceCode);
|
32
|
+
if (!ruleInfo) {
|
33
|
+
return {};
|
34
|
+
}
|
35
|
+
|
36
|
+
const messagesNode = utils.getMessagesNode(ruleInfo, scopeManager);
|
37
|
+
|
38
|
+
let contextIdentifiers;
|
39
|
+
|
40
|
+
if (!messagesNode || messagesNode.type !== 'ObjectExpression') {
|
41
|
+
// If we can't find `meta.messages`, disable the rule.
|
42
|
+
return {};
|
43
|
+
}
|
44
|
+
|
45
|
+
return {
|
46
|
+
Program(ast) {
|
47
|
+
contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
|
48
|
+
},
|
49
|
+
|
50
|
+
CallExpression(node) {
|
51
|
+
const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0
|
52
|
+
// Check for messageId properties used in known calls to context.report();
|
53
|
+
if (
|
54
|
+
node.callee.type === 'MemberExpression' &&
|
55
|
+
contextIdentifiers.has(node.callee.object) &&
|
56
|
+
node.callee.property.type === 'Identifier' &&
|
57
|
+
node.callee.property.name === 'report'
|
58
|
+
) {
|
59
|
+
const reportInfo = utils.getReportInfo(node, context);
|
60
|
+
if (!reportInfo) {
|
61
|
+
return;
|
62
|
+
}
|
63
|
+
|
64
|
+
const reportMessagesAndDataArray =
|
65
|
+
utils.collectReportViolationAndSuggestionData(reportInfo);
|
66
|
+
for (const { messageId } of reportMessagesAndDataArray.filter(
|
67
|
+
(obj) => obj.messageId
|
68
|
+
)) {
|
69
|
+
const values =
|
70
|
+
messageId.type === 'Literal'
|
71
|
+
? [messageId]
|
72
|
+
: utils.findPossibleVariableValues(messageId, scopeManager);
|
73
|
+
|
74
|
+
// Look for any possible string values we found for this messageId.
|
75
|
+
values.forEach((val) => {
|
76
|
+
if (
|
77
|
+
val.type === 'Literal' &&
|
78
|
+
typeof val.value === 'string' &&
|
79
|
+
val.value !== '' &&
|
80
|
+
!utils.getMessageIdNodeById(
|
81
|
+
val.value,
|
82
|
+
ruleInfo,
|
83
|
+
scopeManager,
|
84
|
+
scope
|
85
|
+
)
|
86
|
+
)
|
87
|
+
// Couldn't find this messageId in `meta.messages`.
|
88
|
+
context.report({
|
89
|
+
node: val,
|
90
|
+
messageId: 'missingMessage',
|
91
|
+
data: {
|
92
|
+
messageId: val.value,
|
93
|
+
},
|
94
|
+
});
|
95
|
+
});
|
96
|
+
}
|
97
|
+
}
|
98
|
+
},
|
99
|
+
};
|
100
|
+
},
|
101
|
+
};
|
@@ -0,0 +1,131 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Disallow missing placeholders in rule report messages
|
3
|
+
* @author Teddy Katz
|
4
|
+
*/
|
5
|
+
|
6
|
+
'use strict';
|
7
|
+
|
8
|
+
const utils = require('../utils');
|
9
|
+
const { getStaticValue } = require('eslint-utils');
|
10
|
+
|
11
|
+
// ------------------------------------------------------------------------------
|
12
|
+
// Rule Definition
|
13
|
+
// ------------------------------------------------------------------------------
|
14
|
+
|
15
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
16
|
+
module.exports = {
|
17
|
+
meta: {
|
18
|
+
type: 'problem',
|
19
|
+
docs: {
|
20
|
+
description: 'disallow missing placeholders in rule report messages',
|
21
|
+
category: 'Rules',
|
22
|
+
recommended: true,
|
23
|
+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-missing-placeholders.md',
|
24
|
+
},
|
25
|
+
fixable: null,
|
26
|
+
schema: [],
|
27
|
+
messages: {
|
28
|
+
placeholderDoesNotExist:
|
29
|
+
"The placeholder {{{{missingKey}}}} is missing (must provide it in the report's `data` object).",
|
30
|
+
},
|
31
|
+
},
|
32
|
+
|
33
|
+
create(context) {
|
34
|
+
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
|
35
|
+
const { scopeManager } = sourceCode;
|
36
|
+
|
37
|
+
let contextIdentifiers;
|
38
|
+
|
39
|
+
const ruleInfo = utils.getRuleInfo(sourceCode);
|
40
|
+
if (!ruleInfo) {
|
41
|
+
return {};
|
42
|
+
}
|
43
|
+
|
44
|
+
const messagesNode = utils.getMessagesNode(ruleInfo, scopeManager);
|
45
|
+
|
46
|
+
return {
|
47
|
+
Program(ast) {
|
48
|
+
contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
|
49
|
+
},
|
50
|
+
CallExpression(node) {
|
51
|
+
const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0
|
52
|
+
if (
|
53
|
+
node.callee.type === 'MemberExpression' &&
|
54
|
+
contextIdentifiers.has(node.callee.object) &&
|
55
|
+
node.callee.property.type === 'Identifier' &&
|
56
|
+
node.callee.property.name === 'report'
|
57
|
+
) {
|
58
|
+
const reportInfo = utils.getReportInfo(node, context);
|
59
|
+
if (!reportInfo) {
|
60
|
+
return;
|
61
|
+
}
|
62
|
+
|
63
|
+
const reportMessagesAndDataArray =
|
64
|
+
utils.collectReportViolationAndSuggestionData(reportInfo);
|
65
|
+
|
66
|
+
if (messagesNode) {
|
67
|
+
// Check for any potential instances where we can use the messageId to fill in the message for convenience.
|
68
|
+
reportMessagesAndDataArray.forEach((obj) => {
|
69
|
+
if (
|
70
|
+
!obj.message &&
|
71
|
+
obj.messageId &&
|
72
|
+
obj.messageId.type === 'Literal' &&
|
73
|
+
typeof obj.messageId.value === 'string'
|
74
|
+
) {
|
75
|
+
const correspondingMessage = utils.getMessageIdNodeById(
|
76
|
+
obj.messageId.value,
|
77
|
+
ruleInfo,
|
78
|
+
scopeManager,
|
79
|
+
scope
|
80
|
+
);
|
81
|
+
if (correspondingMessage) {
|
82
|
+
obj.message = correspondingMessage.value;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
});
|
86
|
+
}
|
87
|
+
|
88
|
+
for (const {
|
89
|
+
message,
|
90
|
+
messageId,
|
91
|
+
data,
|
92
|
+
} of reportMessagesAndDataArray.filter((obj) => obj.message)) {
|
93
|
+
const messageStaticValue = getStaticValue(message, scope);
|
94
|
+
if (
|
95
|
+
((message.type === 'Literal' &&
|
96
|
+
typeof message.value === 'string') ||
|
97
|
+
(messageStaticValue &&
|
98
|
+
typeof messageStaticValue.value === 'string')) &&
|
99
|
+
(!data || data.type === 'ObjectExpression')
|
100
|
+
) {
|
101
|
+
// Same regex as the one ESLint uses
|
102
|
+
// https://github.com/eslint/eslint/blob/e5446449d93668ccbdb79d78cc69f165ce4fde07/lib/eslint.js#L990
|
103
|
+
const PLACEHOLDER_MATCHER = /{{\s*([^{}]+?)\s*}}/g;
|
104
|
+
let match;
|
105
|
+
|
106
|
+
while (
|
107
|
+
(match = PLACEHOLDER_MATCHER.exec(
|
108
|
+
message.value || messageStaticValue.value
|
109
|
+
))
|
110
|
+
) {
|
111
|
+
const matchingProperty =
|
112
|
+
data &&
|
113
|
+
data.properties.find(
|
114
|
+
(prop) => utils.getKeyName(prop) === match[1]
|
115
|
+
);
|
116
|
+
|
117
|
+
if (!matchingProperty) {
|
118
|
+
context.report({
|
119
|
+
node: data || messageId || message,
|
120
|
+
messageId: 'placeholderDoesNotExist',
|
121
|
+
data: { missingKey: match[1] },
|
122
|
+
});
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
},
|
129
|
+
};
|
130
|
+
},
|
131
|
+
};
|