@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,162 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const { findVariable } = require('eslint-utils');
|
4
|
+
const utils = require('../utils');
|
5
|
+
|
6
|
+
// ------------------------------------------------------------------------------
|
7
|
+
// Rule Definition
|
8
|
+
// ------------------------------------------------------------------------------
|
9
|
+
|
10
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
11
|
+
module.exports = {
|
12
|
+
meta: {
|
13
|
+
type: 'suggestion',
|
14
|
+
docs: {
|
15
|
+
description: 'require rules to implement a `meta.schema` property',
|
16
|
+
category: 'Rules',
|
17
|
+
recommended: true,
|
18
|
+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/require-meta-schema.md',
|
19
|
+
},
|
20
|
+
hasSuggestions: true,
|
21
|
+
schema: [
|
22
|
+
{
|
23
|
+
type: 'object',
|
24
|
+
properties: {
|
25
|
+
requireSchemaPropertyWhenOptionless: {
|
26
|
+
type: 'boolean',
|
27
|
+
default: true,
|
28
|
+
description:
|
29
|
+
'Whether the rule should require the `meta.schema` property to be specified (with `schema: []`) for rules that have no options.',
|
30
|
+
},
|
31
|
+
},
|
32
|
+
additionalProperties: false,
|
33
|
+
},
|
34
|
+
],
|
35
|
+
messages: {
|
36
|
+
addEmptySchema: 'Add empty schema indicating the rule has no options.',
|
37
|
+
foundOptionsUsage:
|
38
|
+
'`meta.schema` has no schema defined but rule has options.',
|
39
|
+
missing: '`meta.schema` is required (use [] if rule has no schema).',
|
40
|
+
wrongType:
|
41
|
+
'`meta.schema` should be an array or object (use [] if rule has no schema).',
|
42
|
+
},
|
43
|
+
},
|
44
|
+
|
45
|
+
create(context) {
|
46
|
+
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
|
47
|
+
const { scopeManager } = sourceCode;
|
48
|
+
const ruleInfo = utils.getRuleInfo(sourceCode);
|
49
|
+
if (!ruleInfo) {
|
50
|
+
return {};
|
51
|
+
}
|
52
|
+
|
53
|
+
let contextIdentifiers;
|
54
|
+
const metaNode = ruleInfo.meta;
|
55
|
+
let schemaNode;
|
56
|
+
|
57
|
+
// Options
|
58
|
+
const requireSchemaPropertyWhenOptionless =
|
59
|
+
!context.options[0] ||
|
60
|
+
context.options[0].requireSchemaPropertyWhenOptionless;
|
61
|
+
|
62
|
+
let hasEmptySchema = false;
|
63
|
+
let isUsingOptions = false;
|
64
|
+
|
65
|
+
return {
|
66
|
+
Program(ast) {
|
67
|
+
contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
|
68
|
+
|
69
|
+
schemaNode = utils
|
70
|
+
.evaluateObjectProperties(metaNode, scopeManager)
|
71
|
+
.find(
|
72
|
+
(p) => p.type === 'Property' && utils.getKeyName(p) === 'schema'
|
73
|
+
);
|
74
|
+
|
75
|
+
if (!schemaNode) {
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
|
79
|
+
let { value } = schemaNode;
|
80
|
+
if (value.type === 'Identifier' && value.name !== 'undefined') {
|
81
|
+
const variable = findVariable(
|
82
|
+
scopeManager.acquire(value) || scopeManager.globalScope,
|
83
|
+
value
|
84
|
+
);
|
85
|
+
|
86
|
+
// If we can't find the declarator, we have to assume it's in correct type
|
87
|
+
if (
|
88
|
+
!variable ||
|
89
|
+
!variable.defs ||
|
90
|
+
!variable.defs[0] ||
|
91
|
+
!variable.defs[0].node ||
|
92
|
+
variable.defs[0].node.type !== 'VariableDeclarator' ||
|
93
|
+
!variable.defs[0].node.init
|
94
|
+
) {
|
95
|
+
return;
|
96
|
+
}
|
97
|
+
|
98
|
+
value = variable.defs[0].node.init;
|
99
|
+
}
|
100
|
+
|
101
|
+
if (
|
102
|
+
(value.type === 'ArrayExpression' && value.elements.length === 0) ||
|
103
|
+
(value.type === 'ObjectExpression' && value.properties.length === 0)
|
104
|
+
) {
|
105
|
+
// Schema is explicitly defined as having no options.
|
106
|
+
hasEmptySchema = true;
|
107
|
+
}
|
108
|
+
|
109
|
+
if (
|
110
|
+
value.type === 'Literal' ||
|
111
|
+
(value.type === 'Identifier' && value.name === 'undefined')
|
112
|
+
) {
|
113
|
+
context.report({ node: value, messageId: 'wrongType' });
|
114
|
+
}
|
115
|
+
},
|
116
|
+
|
117
|
+
'Program:exit'() {
|
118
|
+
if (!schemaNode && requireSchemaPropertyWhenOptionless) {
|
119
|
+
context.report({
|
120
|
+
node: metaNode || ruleInfo.create,
|
121
|
+
messageId: 'missing',
|
122
|
+
suggest:
|
123
|
+
!isUsingOptions &&
|
124
|
+
metaNode &&
|
125
|
+
metaNode.type === 'ObjectExpression'
|
126
|
+
? [
|
127
|
+
{
|
128
|
+
messageId: 'addEmptySchema',
|
129
|
+
fix(fixer) {
|
130
|
+
return utils.insertProperty(
|
131
|
+
fixer,
|
132
|
+
metaNode,
|
133
|
+
'schema: []',
|
134
|
+
sourceCode
|
135
|
+
);
|
136
|
+
},
|
137
|
+
},
|
138
|
+
]
|
139
|
+
: [],
|
140
|
+
});
|
141
|
+
}
|
142
|
+
},
|
143
|
+
|
144
|
+
MemberExpression(node) {
|
145
|
+
// Check if `context.options` was used when no options were defined in `meta.schema`.
|
146
|
+
if (
|
147
|
+
(hasEmptySchema || !schemaNode) &&
|
148
|
+
node.object.type === 'Identifier' &&
|
149
|
+
contextIdentifiers.has(node.object) &&
|
150
|
+
node.property.type === 'Identifier' &&
|
151
|
+
node.property.name === 'options'
|
152
|
+
) {
|
153
|
+
isUsingOptions = true;
|
154
|
+
context.report({
|
155
|
+
node: schemaNode || metaNode || ruleInfo.create,
|
156
|
+
messageId: 'foundOptionsUsage',
|
157
|
+
});
|
158
|
+
}
|
159
|
+
},
|
160
|
+
};
|
161
|
+
},
|
162
|
+
};
|
@@ -0,0 +1,77 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview require rules to implement a `meta.type` property
|
3
|
+
* @author 薛定谔的猫<weiran.zsd@outlook.com>
|
4
|
+
*/
|
5
|
+
|
6
|
+
'use strict';
|
7
|
+
|
8
|
+
const { getStaticValue } = require('eslint-utils');
|
9
|
+
const utils = require('../utils');
|
10
|
+
const VALID_TYPES = new Set(['problem', 'suggestion', 'layout']);
|
11
|
+
|
12
|
+
// ------------------------------------------------------------------------------
|
13
|
+
// Rule Definition
|
14
|
+
// ------------------------------------------------------------------------------
|
15
|
+
|
16
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
17
|
+
module.exports = {
|
18
|
+
meta: {
|
19
|
+
type: 'problem',
|
20
|
+
docs: {
|
21
|
+
description: 'require rules to implement a `meta.type` property',
|
22
|
+
category: 'Rules',
|
23
|
+
recommended: true,
|
24
|
+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/require-meta-type.md',
|
25
|
+
},
|
26
|
+
fixable: null,
|
27
|
+
schema: [],
|
28
|
+
messages: {
|
29
|
+
missing:
|
30
|
+
'`meta.type` is required (must be either `problem`, `suggestion`, or `layout`).',
|
31
|
+
unexpected:
|
32
|
+
'`meta.type` must be either `problem`, `suggestion`, or `layout`.',
|
33
|
+
},
|
34
|
+
},
|
35
|
+
|
36
|
+
create(context) {
|
37
|
+
// ----------------------------------------------------------------------
|
38
|
+
// Public
|
39
|
+
// ----------------------------------------------------------------------
|
40
|
+
|
41
|
+
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
|
42
|
+
const ruleInfo = utils.getRuleInfo(sourceCode);
|
43
|
+
if (!ruleInfo) {
|
44
|
+
return {};
|
45
|
+
}
|
46
|
+
|
47
|
+
return {
|
48
|
+
Program(node) {
|
49
|
+
const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < v9.0.0
|
50
|
+
const { scopeManager } = sourceCode;
|
51
|
+
|
52
|
+
const metaNode = ruleInfo.meta;
|
53
|
+
const typeNode = utils
|
54
|
+
.evaluateObjectProperties(metaNode, scopeManager)
|
55
|
+
.find((p) => p.type === 'Property' && utils.getKeyName(p) === 'type');
|
56
|
+
|
57
|
+
if (!typeNode) {
|
58
|
+
context.report({
|
59
|
+
node: metaNode || ruleInfo.create,
|
60
|
+
messageId: 'missing',
|
61
|
+
});
|
62
|
+
return;
|
63
|
+
}
|
64
|
+
|
65
|
+
const staticValue = getStaticValue(typeNode.value, scope);
|
66
|
+
if (!staticValue) {
|
67
|
+
// Ignore non-static values since we can't determine what they look like.
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
|
71
|
+
if (!VALID_TYPES.has(staticValue.value)) {
|
72
|
+
context.report({ node: typeNode.value, messageId: 'unexpected' });
|
73
|
+
}
|
74
|
+
},
|
75
|
+
};
|
76
|
+
},
|
77
|
+
};
|
@@ -0,0 +1,107 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Requires the properties of a test case to be placed in a consistent order.
|
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: 'suggestion',
|
18
|
+
docs: {
|
19
|
+
description:
|
20
|
+
'require the properties of a test case to be placed in a consistent order',
|
21
|
+
category: 'Tests',
|
22
|
+
recommended: false,
|
23
|
+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/test-case-property-ordering.md',
|
24
|
+
},
|
25
|
+
fixable: 'code',
|
26
|
+
schema: [
|
27
|
+
{
|
28
|
+
type: 'array',
|
29
|
+
elements: { type: 'string' },
|
30
|
+
},
|
31
|
+
],
|
32
|
+
messages: {
|
33
|
+
inconsistentOrder:
|
34
|
+
'The properties of a test case should be placed in a consistent order: [{{order}}].',
|
35
|
+
},
|
36
|
+
},
|
37
|
+
|
38
|
+
create(context) {
|
39
|
+
// ----------------------------------------------------------------------
|
40
|
+
// Public
|
41
|
+
// ----------------------------------------------------------------------
|
42
|
+
const order = context.options[0] || [
|
43
|
+
'filename',
|
44
|
+
'code',
|
45
|
+
'output',
|
46
|
+
'options',
|
47
|
+
'parser',
|
48
|
+
'languageOptions', // flat-mode only
|
49
|
+
'parserOptions', // eslintrc-mode only
|
50
|
+
'globals', // eslintrc-mode only
|
51
|
+
'env', // eslintrc-mode only
|
52
|
+
'errors',
|
53
|
+
];
|
54
|
+
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
|
55
|
+
|
56
|
+
return {
|
57
|
+
Program(ast) {
|
58
|
+
utils.getTestInfo(context, ast).forEach((testRun) => {
|
59
|
+
[testRun.valid, testRun.invalid].forEach((tests) => {
|
60
|
+
tests.forEach((test) => {
|
61
|
+
const properties = (test && test.properties) || [];
|
62
|
+
const keyNames = properties.map(utils.getKeyName);
|
63
|
+
|
64
|
+
for (let i = 0, lastChecked; i < keyNames.length; i++) {
|
65
|
+
const current = order.indexOf(keyNames[i]);
|
66
|
+
|
67
|
+
// current < lastChecked to catch unordered;
|
68
|
+
// and lastChecked === -1 to catch extra properties before.
|
69
|
+
if (
|
70
|
+
current > -1 &&
|
71
|
+
(current < lastChecked || lastChecked === -1)
|
72
|
+
) {
|
73
|
+
let orderMsg = order.filter((item) =>
|
74
|
+
keyNames.includes(item)
|
75
|
+
);
|
76
|
+
orderMsg = [
|
77
|
+
...orderMsg,
|
78
|
+
...(lastChecked === -1
|
79
|
+
? keyNames.filter((item) => !order.includes(item))
|
80
|
+
: []),
|
81
|
+
];
|
82
|
+
|
83
|
+
context.report({
|
84
|
+
node: properties[i],
|
85
|
+
messageId: 'inconsistentOrder',
|
86
|
+
data: { order: orderMsg.join(', ') },
|
87
|
+
fix(fixer) {
|
88
|
+
return orderMsg.map((key, index) => {
|
89
|
+
const propertyToInsert =
|
90
|
+
properties[keyNames.indexOf(key)];
|
91
|
+
return fixer.replaceText(
|
92
|
+
properties[index],
|
93
|
+
sourceCode.getText(propertyToInsert)
|
94
|
+
);
|
95
|
+
});
|
96
|
+
},
|
97
|
+
});
|
98
|
+
}
|
99
|
+
lastChecked = current;
|
100
|
+
}
|
101
|
+
});
|
102
|
+
});
|
103
|
+
});
|
104
|
+
},
|
105
|
+
};
|
106
|
+
},
|
107
|
+
};
|
@@ -0,0 +1,124 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Enforce consistent usage of shorthand strings for test cases with no options
|
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
|
+
'enforce consistent usage of shorthand strings for test cases with no options',
|
21
|
+
category: 'Tests',
|
22
|
+
recommended: false,
|
23
|
+
url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/test-case-shorthand-strings.md',
|
24
|
+
},
|
25
|
+
fixable: 'code',
|
26
|
+
schema: [
|
27
|
+
{ enum: ['as-needed', 'never', 'consistent', 'consistent-as-needed'] },
|
28
|
+
],
|
29
|
+
messages: {
|
30
|
+
useShorthand:
|
31
|
+
'Use {{preferred}} for this test case instead of {{actual}}.',
|
32
|
+
},
|
33
|
+
},
|
34
|
+
|
35
|
+
create(context) {
|
36
|
+
const shorthandOption = context.options[0] || 'as-needed';
|
37
|
+
const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
|
38
|
+
|
39
|
+
// ----------------------------------------------------------------------
|
40
|
+
// Helpers
|
41
|
+
// ----------------------------------------------------------------------
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Reports test cases as necessary
|
45
|
+
* @param {object[]} cases A list of test case nodes
|
46
|
+
* @returns {void}
|
47
|
+
*/
|
48
|
+
function reportTestCases(cases) {
|
49
|
+
const caseInfoList = cases
|
50
|
+
.map((testCase) => {
|
51
|
+
if (
|
52
|
+
testCase.type === 'Literal' ||
|
53
|
+
testCase.type === 'TemplateLiteral'
|
54
|
+
) {
|
55
|
+
return { node: testCase, shorthand: true, needsLongform: false };
|
56
|
+
}
|
57
|
+
if (testCase.type === 'ObjectExpression') {
|
58
|
+
return {
|
59
|
+
node: testCase,
|
60
|
+
shorthand: false,
|
61
|
+
needsLongform: !(
|
62
|
+
testCase.properties.length === 1 &&
|
63
|
+
utils.getKeyName(testCase.properties[0]) === 'code'
|
64
|
+
),
|
65
|
+
};
|
66
|
+
}
|
67
|
+
return null;
|
68
|
+
})
|
69
|
+
.filter(Boolean);
|
70
|
+
|
71
|
+
const isConsistent =
|
72
|
+
new Set(caseInfoList.map((caseInfo) => caseInfo.shorthand)).size <= 1;
|
73
|
+
const hasCaseNeedingLongform = caseInfoList.some(
|
74
|
+
(caseInfo) => caseInfo.needsLongform
|
75
|
+
);
|
76
|
+
|
77
|
+
caseInfoList
|
78
|
+
.filter(
|
79
|
+
{
|
80
|
+
'as-needed': (caseInfo) =>
|
81
|
+
!caseInfo.shorthand && !caseInfo.needsLongform,
|
82
|
+
never: (caseInfo) => caseInfo.shorthand,
|
83
|
+
consistent: isConsistent
|
84
|
+
? () => false
|
85
|
+
: (caseInfo) => caseInfo.shorthand,
|
86
|
+
'consistent-as-needed': (caseInfo) =>
|
87
|
+
caseInfo.shorthand === hasCaseNeedingLongform,
|
88
|
+
}[shorthandOption]
|
89
|
+
)
|
90
|
+
.forEach((badCaseInfo) => {
|
91
|
+
context.report({
|
92
|
+
node: badCaseInfo.node,
|
93
|
+
messageId: 'useShorthand',
|
94
|
+
data: {
|
95
|
+
preferred: badCaseInfo.shorthand ? 'an object' : 'a string',
|
96
|
+
actual: badCaseInfo.shorthand ? 'a string' : 'an object',
|
97
|
+
},
|
98
|
+
fix(fixer) {
|
99
|
+
return fixer.replaceText(
|
100
|
+
badCaseInfo.node,
|
101
|
+
badCaseInfo.shorthand
|
102
|
+
? `{code: ${sourceCode.getText(badCaseInfo.node)}}`
|
103
|
+
: sourceCode.getText(badCaseInfo.node.properties[0].value)
|
104
|
+
);
|
105
|
+
},
|
106
|
+
});
|
107
|
+
});
|
108
|
+
}
|
109
|
+
|
110
|
+
// ----------------------------------------------------------------------
|
111
|
+
// Public
|
112
|
+
// ----------------------------------------------------------------------
|
113
|
+
|
114
|
+
return {
|
115
|
+
Program(ast) {
|
116
|
+
utils
|
117
|
+
.getTestInfo(context, ast)
|
118
|
+
.map((testRun) => testRun.valid)
|
119
|
+
.filter(Boolean)
|
120
|
+
.forEach(reportTestCases);
|
121
|
+
},
|
122
|
+
};
|
123
|
+
},
|
124
|
+
};
|