@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 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',
@@ -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
- line: error.line ?? 0,
67
- col: error.col ?? 0,
69
+ messageLine: error.line ?? 0,
70
+ messageCol: error.col ?? 0,
71
+ start: 0,
72
+ end: content.length,
68
73
  locale: error.locale ?? 'definition',
69
- severity: 'error',
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
- context.report({
110
- node: value,
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-rc.2",
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-rc.2"
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
+ });