@discord/eslint-plugin-discord-intl 0.26.0-rc.2 → 0.26.0
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.
- package/index.js +4 -0
- package/lib/native-validation.js +55 -6
- package/package.json +2 -2
- package/rules/native/no-avoidable-exact-plurals.js +18 -0
- package/rules/native/no-trimmable-whitespace.js +1 -0
- package/rules/native/no-unsafe-variable-syntax.js +17 -0
- /package/rules/{no-duplicate-message-keys.test.js → native/no-duplicate-message-keys.test.js} +0 -0
package/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
rules: {
|
|
3
|
+
'no-avoidable-exact-plurals': require('./rules/native/no-avoidable-exact-plurals'),
|
|
3
4
|
'no-repeated-plural-names': require('./rules/native/no-repeated-plural-names'),
|
|
4
5
|
'no-repeated-plural-options': require('./rules/native/no-repeated-plural-options'),
|
|
5
6
|
'no-trimmable-whitespace': require('./rules/native/no-trimmable-whitespace'),
|
|
6
7
|
'no-unicode-variable-names': require('./rules/native/no-unicode-variable-names'),
|
|
7
8
|
'no-duplicate-message-keys': require('./rules/native/no-duplicate-message-keys'),
|
|
9
|
+
'no-unsafe-variable-syntax': require('./rules/native/no-unsafe-variable-syntax'),
|
|
8
10
|
|
|
9
11
|
'use-static-access': require('./rules/use-static-access'),
|
|
10
12
|
'no-opaque-messages-objects': require('./rules/no-opaque-messages-objects'),
|
|
@@ -14,10 +16,12 @@ module.exports = {
|
|
|
14
16
|
plugins: ['@discord/discord-intl'],
|
|
15
17
|
rules: {
|
|
16
18
|
// Native rules
|
|
19
|
+
'@discord/discord-intl/no-avoidable-exact-plurals': 'error',
|
|
17
20
|
'@discord/discord-intl/no-trimmable-whitespace': 'error',
|
|
18
21
|
'@discord/discord-intl/no-repeated-plural-names': 'error',
|
|
19
22
|
'@discord/discord-intl/no-repeated-plural-options': 'error',
|
|
20
23
|
'@discord/discord-intl/no-unicode-variable-names': 'error',
|
|
24
|
+
'@discord/discord-intl/no-unsafe-variable-syntax': 'error',
|
|
21
25
|
|
|
22
26
|
// JS rules
|
|
23
27
|
'@discord/discord-intl/use-static-access': 'error',
|
package/lib/native-validation.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** @typedef {import('eslint').Rule.RuleListener} RuleListener */
|
|
2
2
|
/** @typedef {import('eslint').Rule.RuleContext} RuleContext */
|
|
3
|
+
/** @typedef {import('eslint').Rule.ReportDescriptor} ReportDescriptor */
|
|
4
|
+
/** @typedef {import('eslint').Rule.ReportDescriptorLocation} ReportDescriptorLocation */
|
|
5
|
+
/** @typedef {import('eslint').AST.SourceLocation} SourceLocation */
|
|
3
6
|
/** @typedef {import('eslint').SourceCode} SourceCode */
|
|
4
7
|
/** @typedef {import("@discord/intl-loader-core/types").IntlDiagnostic} IntlDiagnostic */
|
|
5
8
|
|
|
@@ -63,10 +66,13 @@ function processAndValidateNative(sourceCode, fileName, content) {
|
|
|
63
66
|
description: error.message,
|
|
64
67
|
key: error.key ?? 'file',
|
|
65
68
|
file: error.file ?? processingFileName,
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
messageLine: error.line ?? 0,
|
|
70
|
+
messageCol: error.col ?? 0,
|
|
71
|
+
start: 0,
|
|
72
|
+
end: content.length,
|
|
68
73
|
locale: error.locale ?? 'definition',
|
|
69
|
-
|
|
74
|
+
category: 'correctness',
|
|
75
|
+
fixes: [],
|
|
70
76
|
});
|
|
71
77
|
}
|
|
72
78
|
}
|
|
@@ -98,6 +104,23 @@ function traverseAndReportMatchingNativeValidations(context, predicate) {
|
|
|
98
104
|
context.sourceCode.text,
|
|
99
105
|
);
|
|
100
106
|
|
|
107
|
+
/**
|
|
108
|
+
* @param {IntlDiagnostic} diagnostic
|
|
109
|
+
* @returns {SourceLocation}
|
|
110
|
+
*/
|
|
111
|
+
function reportedSpan(diagnostic) {
|
|
112
|
+
const messageOffset = context.sourceCode.getIndexFromLoc({
|
|
113
|
+
line: diagnostic.messageLine,
|
|
114
|
+
column: diagnostic.messageCol,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
context.sourceCode.getNodeByRangeIndex(messageOffset);
|
|
118
|
+
return {
|
|
119
|
+
start: context.sourceCode.getLocFromIndex(messageOffset + diagnostic.start),
|
|
120
|
+
end: context.sourceCode.getLocFromIndex(messageOffset + diagnostic.end),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
101
124
|
return traverseMessageDefinitions(context, (_property, value, _definition, name) => {
|
|
102
125
|
if (name == null) return;
|
|
103
126
|
const diagnostics = validations[name];
|
|
@@ -106,10 +129,36 @@ function traverseAndReportMatchingNativeValidations(context, predicate) {
|
|
|
106
129
|
for (const diagnostic of diagnostics) {
|
|
107
130
|
if (!predicate(diagnostic)) continue;
|
|
108
131
|
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
/** @type {ReportDescriptor} */
|
|
133
|
+
const report = {
|
|
134
|
+
loc: reportedSpan(diagnostic),
|
|
111
135
|
message: diagnostic.description,
|
|
112
|
-
}
|
|
136
|
+
};
|
|
137
|
+
if (diagnostic.fixes.length > 0 && value.range != null) {
|
|
138
|
+
// Add 1 because ESLint 8 works on 1-based column indices.
|
|
139
|
+
const valueStart = value.range[0] + 1;
|
|
140
|
+
report.fix = (fixer) => {
|
|
141
|
+
return diagnostic.fixes.map((fix) =>
|
|
142
|
+
fixer.replaceTextRange([valueStart + fix.start, valueStart + fix.end], fix.replacement),
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const suggestableFixes = diagnostic.fixes.filter((fix) => fix.message != null);
|
|
147
|
+
if (suggestableFixes.length > 0 && value.range != null) {
|
|
148
|
+
const valueStart = value.range[0];
|
|
149
|
+
|
|
150
|
+
report.suggest = suggestableFixes.map((fix) => ({
|
|
151
|
+
desc: /** @type {string} */ (fix.message),
|
|
152
|
+
fix: (fixer) =>
|
|
153
|
+
fixer.replaceTextRange(
|
|
154
|
+
[valueStart + fix.start, valueStart + fix.end],
|
|
155
|
+
fix.replacement,
|
|
156
|
+
),
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
context.report(report);
|
|
113
162
|
}
|
|
114
163
|
});
|
|
115
164
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@discord/eslint-plugin-discord-intl",
|
|
3
|
-
"version": "0.26.0
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"description": "ESLint plugin for validating and linting messages and translations using @discord/intl",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@typescript-eslint/scope-manager": "^8.8.1",
|
|
14
|
-
"@discord/intl-loader-core": "0.26.0
|
|
14
|
+
"@discord/intl-loader-core": "0.26.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/eslint": "^8.56.12",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { traverseAndReportMatchingNativeValidations } = require('../../lib/native-validation');
|
|
2
|
+
|
|
3
|
+
module.exports = /** @type {import('eslint').Rule.RuleModule} */ ({
|
|
4
|
+
meta: {
|
|
5
|
+
fixable: 'code',
|
|
6
|
+
docs: {
|
|
7
|
+
description:
|
|
8
|
+
'Disallow `=0` and `=1` plural selectors unless necessary, and replace `0` and `1` values with `#` of them.',
|
|
9
|
+
category: 'Best Practices',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
create(context) {
|
|
13
|
+
return traverseAndReportMatchingNativeValidations(
|
|
14
|
+
context,
|
|
15
|
+
(diagnostic) => diagnostic.name === 'NoAvoidableExactPlurals',
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -3,6 +3,7 @@ const { traverseAndReportMatchingNativeValidations } = require('../../lib/native
|
|
|
3
3
|
module.exports = /** @type {import('eslint').Rule.RuleModule} */ ({
|
|
4
4
|
meta: {
|
|
5
5
|
fixable: 'code',
|
|
6
|
+
hasSuggestions: true,
|
|
6
7
|
docs: {
|
|
7
8
|
description: 'Disallow whitespace at the beginning and end of intl messages',
|
|
8
9
|
category: 'Best Practices',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { traverseAndReportMatchingNativeValidations } = require('../../lib/native-validation');
|
|
2
|
+
|
|
3
|
+
module.exports = /** @type {import('eslint').Rule.RuleModule} */ ({
|
|
4
|
+
meta: {
|
|
5
|
+
fixable: 'code',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Disallow the obsoleted "unsafe variable" syntax (`!!{}!!`)',
|
|
8
|
+
category: 'Best Practices',
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
create(context) {
|
|
12
|
+
return traverseAndReportMatchingNativeValidations(
|
|
13
|
+
context,
|
|
14
|
+
(diagnostic) => diagnostic.name === 'NoUnsafeVariableSyntax',
|
|
15
|
+
);
|
|
16
|
+
},
|
|
17
|
+
});
|
/package/rules/{no-duplicate-message-keys.test.js → native/no-duplicate-message-keys.test.js}
RENAMED
|
File without changes
|