@gitlab/eslint-plugin 9.4.0 → 11.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/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ moduleFileExtensions: ['js'],
3
+ transform: {
4
+ '^.+\\.js$': ['babel-jest', { sourceType: 'script' }],
5
+ },
6
+ };
@@ -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,13 @@ 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'),
10
- 'vue-emit-events-with-two-args': require('./rules/vue-emit-events-with-two-args.js'),
9
+ 'require-valid-i18n-helpers': require('./rules/require-valid-i18n-helpers.js'),
11
10
  'vue-no-data-toggle': require('./rules/vue-no-data-toggle.js'),
12
11
  'vue-no-new-non-primitive-in-template': require('./rules/vue-no-new-non-primitive-in-template.js'),
13
12
  'vue-require-i18n-attribute-strings': require('./rules/vue-require-i18n-attribute-strings.js'),
14
13
  'vue-require-i18n-strings': require('./rules/vue-require-i18n-strings.js'),
15
14
  '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'),
15
+ 'vue-require-valid-i18n-helpers': require('./rules/vue-require-valid-i18n-helpers.js'),
17
16
  'vue-slot-name-casing': require('./rules/vue-slot-name-casing.js'),
18
17
  },
19
18
  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
  //------------------------------------------------------------------------------
@@ -110,6 +115,10 @@ module.exports = {
110
115
  );
111
116
  }
112
117
 
118
+ function isMailto(str) {
119
+ return (str || '').startsWith('mailto:');
120
+ }
121
+
113
122
  function isPlaceholder(value) {
114
123
  return /^{?{\w+}}?$/.test(value);
115
124
  }
@@ -248,6 +257,7 @@ module.exports = {
248
257
  !isNumber(value) &&
249
258
  !isPath(value) &&
250
259
  !isUrl(value) &&
260
+ !isMailto(value) &&
251
261
  !isHtml(value) &&
252
262
  !isCssSelector(value) &&
253
263
  !isCssValue(value) &&
@@ -269,21 +279,10 @@ module.exports = {
269
279
  return node.type === 'Literal';
270
280
  }
271
281
 
272
- function isTemplateLiteral(node) {
273
- return node.type === 'TemplateLiteral';
274
- }
275
-
276
282
  function stripVariables(value) {
277
283
  return value ? value.replace(/(\%{[\w-]+})/g, '') : '';
278
284
  }
279
285
 
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
286
  function getCallExpressionString(node, callee) {
288
287
  if (callee.name === 'sprintf') {
289
288
  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
@@ -56,10 +79,6 @@ function isVLiteral(node) {
56
79
  * @returns {array} In form [prefix, postfix] with the content to prepend and append to the string
57
80
  */
58
81
  function selectFixFunction({ type, value }) {
59
- // TODO: needs tests
60
- // TODO: these are gitlab specific fixes...
61
- // TODO: Need a config for the default externalization function to use
62
- // TODO: probably need withExpressionWithQuotes....
63
82
  const noQuotes = ['__(', ')'];
64
83
  const withQuotes = ['__("', '")'];
65
84
  const withExpression = ['{{ __(', ') }}'];
@@ -67,7 +86,6 @@ function selectFixFunction({ type, value }) {
67
86
 
68
87
  const hasQuotes = isWrappedWithQuotes(value);
69
88
 
70
- // TODO: double check this is still needed
71
89
  if (type === NODE_TYPES.V_TEXT) {
72
90
  return hasQuotes ? withExpression : withQuotesExpression;
73
91
  } else {
@@ -172,6 +190,8 @@ module.exports = {
172
190
  clearSplitLines,
173
191
  closest,
174
192
  isString,
193
+ isTemplateLiteral,
194
+ getTemplateLiteralString,
175
195
  isVLiteral,
176
196
  removeNewLines,
177
197
  selectFixFunction,
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@gitlab/eslint-plugin",
3
- "version": "9.4.0",
3
+ "version": "11.0.0",
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,38 +0,0 @@
1
- # @gitlab/vue-emit-events-with-two-args
2
-
3
- Throw an error when emit is called with more than two arguments.
4
-
5
- ## Rule Details
6
-
7
- It has been decided to use `mitt` as an event bus implementation and this library does not support argument list as the event arguments, [it expects two arguments only](https://github.com/developit/mitt#emit).
8
-
9
- To pass an argument list, an object should be used as the second argument.
10
-
11
- ### Examples of **incorrect** code for this rule
12
-
13
- `emit` method called with more than two arguments.
14
-
15
- ```javascript
16
- vm.$emit('keyspressed', 'enter', '1');
17
- vm.emit('keyspressed', 'enter', '1');
18
- ```
19
-
20
- ### Examples of **correct** code for this rule
21
-
22
- Pass an object as the second argument to `emit`.
23
-
24
- ```javascript
25
- vm.$emit('keyspressed', { keys: ['enter', '1'] });
26
- vm.emit('keyspressed', { keys: ['enter', '1'] });
27
- ```
28
-
29
- `emit` method called with one argument.
30
-
31
- ```javascript
32
- vm.$emit('focus');
33
- vm.emit('focus');
34
- ```
35
-
36
- ## Options
37
-
38
- Nothing
@@ -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
@@ -1,54 +0,0 @@
1
- // ------------------------------------------------------------------------------
2
- // Requirements
3
- // ------------------------------------------------------------------------------
4
- const { DOCS_BASE_URL } = require('../constants');
5
- const { getFunctionName } = require('../utils/rule-utils');
6
-
7
- // ------------------------------------------------------------------------------
8
- // Helpers
9
- // ------------------------------------------------------------------------------
10
-
11
- const EMIT_LIST = new Set(['$emit', 'emit']);
12
- const ERROR_MESSAGE = `Expected emit to be called with no more than two arguments.`;
13
-
14
- // ------------------------------------------------------------------------------
15
- // Rule Definition
16
- // ------------------------------------------------------------------------------
17
-
18
- module.exports = {
19
- meta: {
20
- type: 'problem',
21
- docs: {
22
- description: `Throw an error when emit is called with more than two arguments.`,
23
- url: DOCS_BASE_URL + '/vue-emit-events-with-two-args.md',
24
- },
25
- },
26
- create(context) {
27
- function report(node) {
28
- context.report({
29
- node,
30
- message: ERROR_MESSAGE,
31
- });
32
- }
33
-
34
- function isEmitCall(node) {
35
- const functionName = getFunctionName(node);
36
-
37
- return functionName && EMIT_LIST.has(functionName);
38
- }
39
-
40
- function checkFunctionArguments(callExpression) {
41
- if (callExpression.arguments.length > 2) {
42
- report(callExpression);
43
- }
44
- }
45
-
46
- return {
47
- CallExpression(node) {
48
- if (isEmitCall(node)) {
49
- checkFunctionArguments(node);
50
- }
51
- },
52
- };
53
- },
54
- };