@gitlab/eslint-plugin 9.2.0 → 10.0.1

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/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ moduleFileExtensions: ['js'],
3
+ transform: {
4
+ '^.+\\.js$': ['babel-jest', { sourceType: 'script' }],
5
+ },
6
+ };
@@ -105,7 +105,6 @@ module.exports = {
105
105
  'error',
106
106
  {
107
107
  groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
108
- alphabetize: { order: 'asc' },
109
108
  },
110
109
  ],
111
110
  // BEGIN rules to aid migration from Vue 2.x to 3.x.
@@ -4,6 +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',
7
+ '@gitlab/require-valid-i18n-helpers': 'error',
8
+ '@gitlab/vue-require-valid-i18n-helpers': 'error',
8
9
  },
9
10
  };
package/lib/index.js CHANGED
@@ -3,16 +3,17 @@
3
3
  */
4
4
  module.exports = {
5
5
  rules: {
6
- 'require-string-literal-i18n-helpers': require('./rules/require-string-literal-i18n-helpers.js'),
7
6
  'no-global-event-off': require('./rules/no-global-event-off.js'),
8
7
  'no-runtime-template-compiler': require('./rules/no-runtime-template-compiler.js'),
9
8
  'require-i18n-strings': require('./rules/require-i18n-strings.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-valid-i18n-helpers': require('./rules/vue-require-valid-i18n-helpers.js'),
16
17
  'vue-slot-name-casing': require('./rules/vue-slot-name-casing.js'),
17
18
  },
18
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);
@@ -0,0 +1,29 @@
1
+ // ------------------------------------------------------------------------------
2
+ // Requirements
3
+ // ------------------------------------------------------------------------------
4
+ const { DOCS_BASE_URL } = require('../constants');
5
+ const { validateI18nHelperCallFactory } = require('../utils/i18n-utils');
6
+
7
+ // ------------------------------------------------------------------------------
8
+ // Rule Definition
9
+ // ------------------------------------------------------------------------------
10
+
11
+ module.exports = {
12
+ meta: {
13
+ type: 'error',
14
+ docs: {
15
+ description: 'Enforces valid usage of translation helpers in JavaScript.',
16
+ category: 'i18n',
17
+ url: DOCS_BASE_URL + '/require-valid-i18n-helpers.md',
18
+ },
19
+ },
20
+ create(context) {
21
+ const validateI18nHelperCall = validateI18nHelperCallFactory(context);
22
+
23
+ return {
24
+ CallExpression(node) {
25
+ validateI18nHelperCall(node);
26
+ },
27
+ };
28
+ },
29
+ };
@@ -0,0 +1,30 @@
1
+ // ------------------------------------------------------------------------------
2
+ // Requirements
3
+ // ------------------------------------------------------------------------------
4
+ const { DOCS_BASE_URL } = require('../constants');
5
+ const { defineTemplateBodyVisitor } = require('../utils/index');
6
+ const { validateI18nHelperCallFactory } = require('../utils/i18n-utils');
7
+
8
+ // ------------------------------------------------------------------------------
9
+ // Rule Definition
10
+ // ------------------------------------------------------------------------------
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: 'error',
15
+ docs: {
16
+ description: 'Enforces valid usage of translation helpers in Vue templates.',
17
+ category: 'i18n',
18
+ url: DOCS_BASE_URL + '/vue-require-valid-i18n-helpers.md',
19
+ },
20
+ },
21
+ create(context) {
22
+ const validateI18nHelperCall = validateI18nHelperCallFactory(context);
23
+
24
+ return defineTemplateBodyVisitor(context, {
25
+ CallExpression(node) {
26
+ validateI18nHelperCall(node);
27
+ },
28
+ });
29
+ },
30
+ };
@@ -0,0 +1,68 @@
1
+ const {
2
+ getFunctionName,
3
+ isString,
4
+ isTemplateLiteral,
5
+ getTemplateLiteralString,
6
+ } = require('../utils/rule-utils');
7
+
8
+ const GETTEXT = '__';
9
+ const P_GETTEXT = 's__';
10
+ const N_GETTEXT = 'n__';
11
+
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.';
14
+
15
+ const mustBeCalledWithStringLiteralArguments = (context, node) => {
16
+ const report = (node) => {
17
+ context.report({
18
+ node,
19
+ message: ERROR_MESSAGE_STRING_LITERAL,
20
+ });
21
+ };
22
+
23
+ if (!node.arguments.length) {
24
+ report(node);
25
+ }
26
+ const [arg1, arg2] = node.arguments;
27
+ if ([arg1, arg2].some((arg) => arg && !isString(arg))) {
28
+ report(node);
29
+ }
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
+
64
+ module.exports = {
65
+ validateI18nHelperCallFactory,
66
+ ERROR_MESSAGE_STRING_LITERAL,
67
+ ERROR_MESSAGE_MISSING_NAMESPACE,
68
+ };
@@ -7,6 +7,7 @@ const V_TEXT = 'VText';
7
7
  const V_ELEMENT = 'VElement';
8
8
  const TEMPLATE_LITERAL = 'TemplateLiteral';
9
9
  const LITERAL = 'Literal';
10
+ const IDENTIFIER = 'Identifier';
10
11
 
11
12
  const NODE_TYPES = {
12
13
  CALL_EXPRESSION,
@@ -18,6 +19,7 @@ const NODE_TYPES = {
18
19
  V_ELEMENT,
19
20
  TEMPLATE_LITERAL,
20
21
  LITERAL,
22
+ IDENTIFIER,
21
23
  };
22
24
 
23
25
  function closest(node, predicate) {
@@ -39,6 +41,29 @@ function isString(node) {
39
41
  return [LITERAL, TEMPLATE_LITERAL].includes(node.type);
40
42
  }
41
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
+
42
67
  /**
43
68
  * Check that a node is a `VLiteral type`
44
69
  * @param {ASTNode} node The node to type check
@@ -54,10 +79,6 @@ function isVLiteral(node) {
54
79
  * @returns {array} In form [prefix, postfix] with the content to prepend and append to the string
55
80
  */
56
81
  function selectFixFunction({ type, value }) {
57
- // TODO: needs tests
58
- // TODO: these are gitlab specific fixes...
59
- // TODO: Need a config for the default externalization function to use
60
- // TODO: probably need withExpressionWithQuotes....
61
82
  const noQuotes = ['__(', ')'];
62
83
  const withQuotes = ['__("', '")'];
63
84
  const withExpression = ['{{ __(', ') }}'];
@@ -65,7 +86,6 @@ function selectFixFunction({ type, value }) {
65
86
 
66
87
  const hasQuotes = isWrappedWithQuotes(value);
67
88
 
68
- // TODO: double check this is still needed
69
89
  if (type === NODE_TYPES.V_TEXT) {
70
90
  return hasQuotes ? withExpression : withQuotesExpression;
71
91
  } else {
@@ -170,6 +190,8 @@ module.exports = {
170
190
  clearSplitLines,
171
191
  closest,
172
192
  isString,
193
+ isTemplateLiteral,
194
+ getTemplateLiteralString,
173
195
  isVLiteral,
174
196
  removeNewLines,
175
197
  selectFixFunction,
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@gitlab/eslint-plugin",
3
- "version": "9.2.0",
3
+ "version": "10.0.1",
4
4
  "description": "GitLab package for our custom eslint rules",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
7
7
  "createRule": "./scripts/createRule.js && yarn update",
8
8
  "update": "./scripts/updateFiles.js && prettier --write lib/index.js",
9
9
  "commit": "npx git-cz",
10
- "test": "mocha tests --recursive"
10
+ "test": "jest tests"
11
11
  },
12
12
  "repository": "https://gitlab.com/gitlab-org/frontend/eslint-plugin.git",
13
13
  "keywords": [
@@ -48,7 +48,7 @@
48
48
  "devDependencies": {
49
49
  "eslint": "^7.27.0",
50
50
  "glob": "^7.1.3",
51
- "mocha": "^6.2.3",
51
+ "jest": "^26.6.3",
52
52
  "prettier": "^2.2.1",
53
53
  "pretty-quick": "^3.1.0"
54
54
  },
@@ -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,61 +0,0 @@
1
- // ------------------------------------------------------------------------------
2
- // Requirements
3
- // ------------------------------------------------------------------------------
4
- const { DOCS_BASE_URL } = require('../constants');
5
- const { getFunctionName, isString } = require('../utils/rule-utils');
6
-
7
- // ------------------------------------------------------------------------------
8
- // Helpers
9
- // ------------------------------------------------------------------------------
10
-
11
- const I18N_HELPERS = new Set(['__', 's__', 'n__']);
12
- const ERROR_MESSAGE = 'Translation helpers must be called with a string literal.';
13
-
14
- const isI18nCall = (node) => {
15
- const functionName = getFunctionName(node);
16
- return functionName && I18N_HELPERS.has(functionName);
17
- };
18
-
19
- const checkFunctionArgumentFactory = (context) => (node) => {
20
- if (!isI18nCall(node)) {
21
- return;
22
- }
23
- const report = (node) => {
24
- context.report({
25
- node,
26
- message: ERROR_MESSAGE,
27
- });
28
- };
29
-
30
- if (!node.arguments.length) {
31
- report(node);
32
- }
33
- const [arg1, arg2] = node.arguments;
34
- if ([arg1, arg2].some((arg) => arg && !isString(arg))) {
35
- report(node);
36
- }
37
- };
38
-
39
- // ------------------------------------------------------------------------------
40
- // Rule Definition
41
- // ------------------------------------------------------------------------------
42
-
43
- module.exports = {
44
- meta: {
45
- type: 'error',
46
- docs: {
47
- description: 'Prevents translation helpers from being called with non-string-literal arguments.',
48
- category: 'i18n',
49
- url: DOCS_BASE_URL + '/require-string-literal-i18n-helpers.md',
50
- },
51
- },
52
- create(context) {
53
- const checkFunctionArgument = checkFunctionArgumentFactory(context);
54
-
55
- return {
56
- CallExpression(node) {
57
- checkFunctionArgument(node);
58
- },
59
- };
60
- },
61
- };