@gitlab/eslint-plugin 9.4.0 → 10.0.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/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ # [10.0.0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/compare/v9.4.0...v10.0.0) (2021-11-03)
2
+
3
+
4
+ ### Features
5
+
6
+ * lint against missing translation namespaces ([db00d87](https://gitlab.com/gitlab-org/frontend/eslint-plugin/commit/db00d87e3877fa0fe538775280a54fd820c89572))
7
+
8
+
9
+ ### BREAKING CHANGES
10
+
11
+ * require-string-literal-i18n-helpers and
12
+ vue-require-string-literal-i18n-helpers rules have been renamed to
13
+ require-valid-i18n-helpers and vue-require-valid-i18n-helpers
14
+ respectively.
15
+
1
16
  # [9.4.0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/compare/v9.3.0...v9.4.0) (2021-09-21)
2
17
 
3
18
 
@@ -0,0 +1,41 @@
1
+ # @gitlab/require-valid-i18n-helpers
2
+
3
+ This rule enforces valid usages of the translation helpers (`__`, `s__` and `n__`). It requires the
4
+ following:
5
+
6
+ - All helpers should be called with string-literal arguments to ensure the strings are properly
7
+ collected by the `gettext:regenerate` script.
8
+ - `s__` requires a translation namespace to be provided.
9
+
10
+ ## Rule Details
11
+
12
+ ### Examples of **incorrect** code for this rule
13
+
14
+ ```js
15
+ // Invalid argument types
16
+ const label = __(LABEL);
17
+ const description = __(getDescription());
18
+
19
+ // Missing translation namespace
20
+ const namespacedTranslation = s__('NamespacedTranslation');
21
+ ```
22
+
23
+ ### Examples of **correct** code for this rule
24
+
25
+ ```js
26
+ // Valid argument type
27
+ const label = __('Some label');
28
+
29
+ // Valid translation namespace
30
+ const namespacedTranslation = s__('Namespace|Translation');
31
+ // or
32
+ const otherNamespacedTranslation = s__('Namespace', 'Other translation');
33
+ ```
34
+
35
+ ## Options
36
+
37
+ Nothing
38
+
39
+ ## Related rules
40
+
41
+ - TBA
@@ -0,0 +1,42 @@
1
+ # @gitlab/vue-require-valid-i18n-helpers
2
+
3
+ This rule is like [require-valid-i18n-helpers](./require-valid-i18n-helpers.md) for Vue templates.
4
+
5
+ ## Rule Details
6
+
7
+ ### Examples of **incorrect** code for this rule
8
+
9
+ ```html
10
+ <template>
11
+ <div>
12
+ <!-- Invalid argument types -->
13
+ {{ __(LABEL) }}
14
+ {{ __(getDescription()) }}
15
+
16
+ <!-- Missing translation namespace -->
17
+ {{ s__('NamespacedTranslation') }}
18
+ </div>
19
+ </template>
20
+ ```
21
+
22
+ ### Examples of **correct** code for this rule
23
+
24
+ ```html
25
+ <template>
26
+ <div>
27
+ <!-- Valid argument type -->
28
+ {{ __('Some label') }}
29
+
30
+ <!-- Valid translation namespace -->
31
+ {{ s__('Namespace|Translation') }}
32
+ </div>
33
+ </template>
34
+ ```
35
+
36
+ ## Options
37
+
38
+ Nothing
39
+
40
+ ## Related rules
41
+
42
+ - TBA
package/docs/rules.md CHANGED
@@ -7,12 +7,12 @@ Available rules:
7
7
  - [no-global-event-off](./rules/no-global-event-off.md): Throw an error when globally deregistering all event listeners.
8
8
  - [no-runtime-template-compiler](./rules/no-runtime-template-compiler.md): Disallow components which rely on a runtime template compiler.
9
9
  - [require-i18n-strings](./rules/require-i18n-strings.md): Detect a string which has been hard coded and requires externalization.
10
- - [require-string-literal-i18n-helpers](./rules/require-string-literal-i18n-helpers.md): Prevents translation helpers from being called with non-string-literal arguments.
10
+ - [require-valid-i18n-helpers](./rules/require-valid-i18n-helpers.md): Enforces valid usage of translation helpers in JavaScript.
11
11
  - [vue-emit-events-with-two-args](./rules/vue-emit-events-with-two-args.md): Throw an error when emit is called with more than two arguments.
12
12
  - [vue-no-data-toggle](./rules/vue-no-data-toggle.md): Restrict the use of `data-toggle` bootstrap behaviors within Vue templates
13
13
  - [vue-no-new-non-primitive-in-template](./rules/vue-no-new-non-primitive-in-template.md): Prevents non-primitive values from being declared in templates
14
14
  - [vue-require-i18n-attribute-strings](./rules/vue-require-i18n-attribute-strings.md): Detect non externalized strings in vue `<template>` attributes
15
15
  - [vue-require-i18n-strings](./rules/vue-require-i18n-strings.md): enforce no bare strings in vue `<template>`
16
16
  - [vue-require-required-key](./rules/vue-require-required-key.md): Require the required key to be set
17
- - [vue-require-string-literal-i18n-helpers](./rules/vue-require-string-literal-i18n-helpers.md): Prevents translation helpers from being called with non-string-literal arguments in Vue templates.
17
+ - [vue-require-valid-i18n-helpers](./rules/vue-require-valid-i18n-helpers.md): Enforces valid usage of translation helpers in Vue templates.
18
18
  - [vue-slot-name-casing](./rules/vue-slot-name-casing.md): enforce specific casing for slot naming style in template
@@ -4,7 +4,7 @@ module.exports = {
4
4
  '@gitlab/require-i18n-strings': 'error',
5
5
  '@gitlab/vue-require-i18n-attribute-strings': 'error',
6
6
  '@gitlab/vue-require-i18n-strings': 'error',
7
- '@gitlab/require-string-literal-i18n-helpers': 'error',
8
- '@gitlab/vue-require-string-literal-i18n-helpers': 'error',
7
+ '@gitlab/require-valid-i18n-helpers': 'error',
8
+ '@gitlab/vue-require-valid-i18n-helpers': 'error',
9
9
  },
10
10
  };
package/lib/index.js CHANGED
@@ -6,14 +6,14 @@ module.exports = {
6
6
  'no-global-event-off': require('./rules/no-global-event-off.js'),
7
7
  'no-runtime-template-compiler': require('./rules/no-runtime-template-compiler.js'),
8
8
  'require-i18n-strings': require('./rules/require-i18n-strings.js'),
9
- 'require-string-literal-i18n-helpers': require('./rules/require-string-literal-i18n-helpers.js'),
9
+ 'require-valid-i18n-helpers': require('./rules/require-valid-i18n-helpers.js'),
10
10
  'vue-emit-events-with-two-args': require('./rules/vue-emit-events-with-two-args.js'),
11
11
  'vue-no-data-toggle': require('./rules/vue-no-data-toggle.js'),
12
12
  'vue-no-new-non-primitive-in-template': require('./rules/vue-no-new-non-primitive-in-template.js'),
13
13
  'vue-require-i18n-attribute-strings': require('./rules/vue-require-i18n-attribute-strings.js'),
14
14
  'vue-require-i18n-strings': require('./rules/vue-require-i18n-strings.js'),
15
15
  'vue-require-required-key': require('./rules/vue-require-required-key.js'),
16
- 'vue-require-string-literal-i18n-helpers': require('./rules/vue-require-string-literal-i18n-helpers.js'),
16
+ 'vue-require-valid-i18n-helpers': require('./rules/vue-require-valid-i18n-helpers.js'),
17
17
  'vue-slot-name-casing': require('./rules/vue-slot-name-casing.js'),
18
18
  },
19
19
  configs: {
@@ -4,6 +4,11 @@
4
4
  */
5
5
  'use strict';
6
6
 
7
+ // ------------------------------------------------------------------------------
8
+ // Requirements
9
+ // ------------------------------------------------------------------------------
10
+ const { isTemplateLiteral, getTemplateLiteralString } = require('../utils/rule-utils');
11
+
7
12
  //------------------------------------------------------------------------------
8
13
  // Rule Definition
9
14
  //------------------------------------------------------------------------------
@@ -269,21 +274,10 @@ module.exports = {
269
274
  return node.type === 'Literal';
270
275
  }
271
276
 
272
- function isTemplateLiteral(node) {
273
- return node.type === 'TemplateLiteral';
274
- }
275
-
276
277
  function stripVariables(value) {
277
278
  return value ? value.replace(/(\%{[\w-]+})/g, '') : '';
278
279
  }
279
280
 
280
- function getTemplateLiteralString(node) {
281
- return node.quasis
282
- .filter((quasi) => quasi.type === 'TemplateElement')
283
- .map((quasi) => quasi.value.raw)
284
- .join('');
285
- }
286
-
287
281
  function getCallExpressionString(node, callee) {
288
282
  if (callee.name === 'sprintf') {
289
283
  return stripVariables(node.value);
@@ -2,7 +2,6 @@
2
2
  // Requirements
3
3
  // ------------------------------------------------------------------------------
4
4
  const { DOCS_BASE_URL } = require('../constants');
5
- const { getFunctionName, isString } = require('../utils/rule-utils');
6
5
  const { validateI18nHelperCallFactory } = require('../utils/i18n-utils');
7
6
 
8
7
  // ------------------------------------------------------------------------------
@@ -13,9 +12,9 @@ module.exports = {
13
12
  meta: {
14
13
  type: 'error',
15
14
  docs: {
16
- description: 'Prevents translation helpers from being called with non-string-literal arguments.',
15
+ description: 'Enforces valid usage of translation helpers in JavaScript.',
17
16
  category: 'i18n',
18
- url: DOCS_BASE_URL + '/require-string-literal-i18n-helpers.md',
17
+ url: DOCS_BASE_URL + '/require-valid-i18n-helpers.md',
19
18
  },
20
19
  },
21
20
  create(context) {
@@ -13,10 +13,9 @@ module.exports = {
13
13
  meta: {
14
14
  type: 'error',
15
15
  docs: {
16
- description:
17
- 'Prevents translation helpers from being called with non-string-literal arguments in Vue templates.',
16
+ description: 'Enforces valid usage of translation helpers in Vue templates.',
18
17
  category: 'i18n',
19
- url: DOCS_BASE_URL + '/vue-require-string-literal-i18n-helpers.md',
18
+ url: DOCS_BASE_URL + '/vue-require-valid-i18n-helpers.md',
20
19
  },
21
20
  },
22
21
  create(context) {
@@ -1,21 +1,22 @@
1
- const { getFunctionName, isString } = require('../utils/rule-utils');
1
+ const {
2
+ getFunctionName,
3
+ isString,
4
+ isTemplateLiteral,
5
+ getTemplateLiteralString,
6
+ } = require('../utils/rule-utils');
2
7
 
3
- const I18N_HELPERS = new Set(['__', 's__', 'n__']);
4
- const ERROR_MESSAGE = 'Translation helpers must be called with a string literal.';
8
+ const GETTEXT = '__';
9
+ const P_GETTEXT = 's__';
10
+ const N_GETTEXT = 'n__';
5
11
 
6
- const isI18nCall = (node) => {
7
- const functionName = getFunctionName(node);
8
- return functionName && I18N_HELPERS.has(functionName);
9
- };
12
+ const ERROR_MESSAGE_STRING_LITERAL = 'Translation helpers must be called with a string literal.';
13
+ const ERROR_MESSAGE_MISSING_NAMESPACE = 's__ requires a translation namespace to be provided.';
10
14
 
11
- const validateI18nHelperCallFactory = (context) => (node) => {
12
- if (!isI18nCall(node)) {
13
- return;
14
- }
15
+ const mustBeCalledWithStringLiteralArguments = (context, node) => {
15
16
  const report = (node) => {
16
17
  context.report({
17
18
  node,
18
- message: ERROR_MESSAGE,
19
+ message: ERROR_MESSAGE_STRING_LITERAL,
19
20
  });
20
21
  };
21
22
 
@@ -28,6 +29,40 @@ const validateI18nHelperCallFactory = (context) => (node) => {
28
29
  }
29
30
  };
30
31
 
32
+ const mustHaveNamespace = (context, node) => {
33
+ if (node.arguments.length === 2) {
34
+ return;
35
+ }
36
+
37
+ const [arg] = node.arguments;
38
+ const value = isTemplateLiteral(arg) ? getTemplateLiteralString(arg).trim() : arg.value;
39
+ if (!/^\w.+\|/.test(value)) {
40
+ context.report({
41
+ node,
42
+ message: ERROR_MESSAGE_MISSING_NAMESPACE,
43
+ });
44
+ }
45
+ };
46
+
47
+ const VALIDATORS = {
48
+ [GETTEXT]: [mustBeCalledWithStringLiteralArguments],
49
+ [P_GETTEXT]: [mustBeCalledWithStringLiteralArguments, mustHaveNamespace],
50
+ [N_GETTEXT]: [mustBeCalledWithStringLiteralArguments],
51
+ };
52
+
53
+ const validateI18nHelperCallFactory = (context) => (node) => {
54
+ const functionName = getFunctionName(node);
55
+ if (!Object.keys(VALIDATORS).includes(functionName)) {
56
+ return;
57
+ }
58
+
59
+ VALIDATORS[functionName].forEach((validator) => {
60
+ validator(context, node);
61
+ });
62
+ };
63
+
31
64
  module.exports = {
32
65
  validateI18nHelperCallFactory,
33
- }
66
+ ERROR_MESSAGE_STRING_LITERAL,
67
+ ERROR_MESSAGE_MISSING_NAMESPACE,
68
+ };
@@ -41,6 +41,29 @@ function isString(node) {
41
41
  return [LITERAL, TEMPLATE_LITERAL].includes(node.type);
42
42
  }
43
43
 
44
+ /**
45
+ * Checks whether a node is a template literal.
46
+ *
47
+ * @param {ASTNode} node The node to type check
48
+ * @returns {boolean} `true` if node is a template literal.
49
+ */
50
+ function isTemplateLiteral(node) {
51
+ return node.type === TEMPLATE_LITERAL;
52
+ }
53
+
54
+ /**
55
+ * Extracts the string out of a template literal node.
56
+ *
57
+ * @param {ASTNode} node The template literal node
58
+ * @returns {string} The extracted string.
59
+ */
60
+ function getTemplateLiteralString(node) {
61
+ return node.quasis
62
+ .filter((quasi) => quasi.type === 'TemplateElement')
63
+ .map((quasi) => quasi.value.raw)
64
+ .join('');
65
+ }
66
+
44
67
  /**
45
68
  * Check that a node is a `VLiteral type`
46
69
  * @param {ASTNode} node The node to type check
@@ -172,6 +195,8 @@ module.exports = {
172
195
  clearSplitLines,
173
196
  closest,
174
197
  isString,
198
+ isTemplateLiteral,
199
+ getTemplateLiteralString,
175
200
  isVLiteral,
176
201
  removeNewLines,
177
202
  selectFixFunction,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/eslint-plugin",
3
- "version": "9.4.0",
3
+ "version": "10.0.0",
4
4
  "description": "GitLab package for our custom eslint rules",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -1,26 +0,0 @@
1
- # @gitlab/require-string-literal-i18n-helpers
2
-
3
- Prevents translation helpers from being called with non-string-literal arguments.
4
-
5
- ## Rule Details
6
-
7
- ### Examples of **incorrect** code for this rule
8
-
9
- ```js
10
- const label = __(LABEL);
11
- const description = __(getDescription());
12
- ```
13
-
14
- ### Examples of **correct** code for this rule
15
-
16
- ```js
17
- const label = __('Some label');
18
- ```
19
-
20
- ## Options
21
-
22
- Nothing
23
-
24
- ## Related rules
25
-
26
- - TBA
@@ -1,34 +0,0 @@
1
- # @gitlab/vue-require-string-literal-i18n-helpers
2
-
3
- Prevents translation helpers from being called with non-string-literal arguments in Vue templates.
4
-
5
- ## Rule Details
6
-
7
- ### Examples of **incorrect** code for this rule
8
-
9
- ```html
10
- <template>
11
- <div>
12
- {{ __(LABEL) }}
13
- {{ __(getDescription()) }}
14
- </div>
15
- </template>
16
- ```
17
-
18
- ### Examples of **correct** code for this rule
19
-
20
- ```html
21
- <template>
22
- <div>
23
- {{ __('Some label') }}
24
- </div>
25
- </template>
26
- ```
27
-
28
- ## Options
29
-
30
- Nothing
31
-
32
- ## Related rules
33
-
34
- - TBA