eslint-plugin-formatjs 3.1.3 → 4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-formatjs",
3
- "version": "3.1.3",
3
+ "version": "4.0.0",
4
4
  "description": "ESLint plugin for formatjs",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -20,11 +20,13 @@
20
20
  },
21
21
  "homepage": "https://github.com/formatjs/formatjs#readme",
22
22
  "dependencies": {
23
- "@formatjs/icu-messageformat-parser": "2.1.2",
24
- "@formatjs/ts-transformer": "3.9.6",
23
+ "@formatjs/icu-messageformat-parser": "2.1.3",
24
+ "@formatjs/ts-transformer": "3.9.8",
25
25
  "@types/eslint": "7 || 8",
26
+ "@types/picomatch": "^2.3.0",
26
27
  "@typescript-eslint/typescript-estree": "^5.9.1",
27
28
  "emoji-regex": "^10.0.0",
29
+ "picomatch": "^2.3.1",
28
30
  "tslib": "2.4.0",
29
31
  "typescript": "^4.5"
30
32
  },
@@ -1 +1 @@
1
- {"version":3,"file":"no-complex-selectors.d.ts","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-formatjs/rules/no-complex-selectors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AA8D3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAiDhB,CAAA;AAED,eAAe,IAAI,CAAA"}
1
+ {"version":3,"file":"no-complex-selectors.d.ts","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-formatjs/rules/no-complex-selectors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAwE3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAiDhB,CAAA;AAED,eAAe,IAAI,CAAA"}
@@ -26,9 +26,20 @@ function checkNode(context, node) {
26
26
  if (!defaultMessage || !messageNode) {
27
27
  continue;
28
28
  }
29
- var hoistedAst = (0, manipulator_1.hoistSelectors)((0, icu_messageformat_parser_1.parse)(defaultMessage, {
30
- ignoreTag: context.settings.ignoreTag,
31
- }));
29
+ var ast = void 0;
30
+ try {
31
+ ast = (0, icu_messageformat_parser_1.parse)(defaultMessage, {
32
+ ignoreTag: context.settings.ignoreTag,
33
+ });
34
+ }
35
+ catch (e) {
36
+ context.report({
37
+ node: messageNode,
38
+ message: e instanceof Error ? e.message : String(e),
39
+ });
40
+ return;
41
+ }
42
+ var hoistedAst = (0, manipulator_1.hoistSelectors)(ast);
32
43
  var complexity = calculateComplexity(hoistedAst);
33
44
  if (complexity > config.limit) {
34
45
  context.report({
@@ -0,0 +1,4 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
4
+ //# sourceMappingURL=no-literal-string-in-jsx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-literal-string-in-jsx.d.ts","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-formatjs/rules/no-literal-string-in-jsx.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAqDhC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA4JhB,CAAA;AAED,eAAe,IAAI,CAAA"}
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var tslib_1 = require("tslib");
4
+ var picomatch_1 = (0, tslib_1.__importDefault)(require("picomatch"));
5
+ var typescript_estree_1 = require("@typescript-eslint/typescript-estree");
6
+ var propMatcherSchema = {
7
+ type: 'array',
8
+ items: {
9
+ type: 'array',
10
+ items: [{ type: 'string' }, { type: 'string' }],
11
+ },
12
+ };
13
+ var defaultPropIncludePattern = [
14
+ ['*', 'aria-{label,description,details,errormessage}'],
15
+ ['[a-z]*([a-z0-9])', '(placeholder|title)'],
16
+ ['img', 'alt'],
17
+ ];
18
+ var defaultPropExcludePattern = [];
19
+ function stringifyJsxTagName(tagName) {
20
+ switch (tagName.type) {
21
+ case typescript_estree_1.TSESTree.AST_NODE_TYPES.JSXIdentifier:
22
+ return tagName.name;
23
+ case typescript_estree_1.TSESTree.AST_NODE_TYPES.JSXMemberExpression:
24
+ return "".concat(stringifyJsxTagName(tagName.object), ".").concat(tagName.property.name);
25
+ case typescript_estree_1.TSESTree.AST_NODE_TYPES.JSXNamespacedName:
26
+ return "".concat(tagName.namespace.name, ":").concat(tagName.name.name);
27
+ }
28
+ }
29
+ function compilePropMatcher(propMatcher) {
30
+ return propMatcher.map(function (_a) {
31
+ var tagNamePattern = _a[0], propNamePattern = _a[1];
32
+ return [
33
+ picomatch_1.default.makeRe(tagNamePattern, { contains: false }),
34
+ picomatch_1.default.makeRe(propNamePattern, { contains: false }),
35
+ ];
36
+ });
37
+ }
38
+ var rule = {
39
+ meta: {
40
+ type: 'problem',
41
+ docs: {
42
+ description: 'Disallow untranslated literal strings without translation.',
43
+ category: 'Errors',
44
+ recommended: false,
45
+ url: 'https://formatjs.io/docs/tooling/linter#no-literal-string-in-jsx',
46
+ },
47
+ schema: [
48
+ {
49
+ type: 'object',
50
+ properties: {
51
+ props: {
52
+ type: 'object',
53
+ properties: {
54
+ include: (0, tslib_1.__assign)({}, propMatcherSchema),
55
+ exclude: (0, tslib_1.__assign)({}, propMatcherSchema),
56
+ },
57
+ },
58
+ },
59
+ },
60
+ ],
61
+ },
62
+ // TODO: Vue support
63
+ create: function (context) {
64
+ var _a, _b, _c, _d;
65
+ var userConfig = context.options[0] || {};
66
+ var propIncludePattern = compilePropMatcher((0, tslib_1.__spreadArray)((0, tslib_1.__spreadArray)([], defaultPropIncludePattern, true), ((_b = (_a = userConfig.props) === null || _a === void 0 ? void 0 : _a.include) !== null && _b !== void 0 ? _b : []), true));
67
+ var propExcludePattern = compilePropMatcher((0, tslib_1.__spreadArray)((0, tslib_1.__spreadArray)([], defaultPropExcludePattern, true), ((_d = (_c = userConfig.props) === null || _c === void 0 ? void 0 : _c.exclude) !== null && _d !== void 0 ? _d : []), true));
68
+ var lexicalJsxStack = [];
69
+ var shouldSkipCurrentJsxAttribute = function (node) {
70
+ var currentJsxNode = lexicalJsxStack[lexicalJsxStack.length - 1];
71
+ if (currentJsxNode.type === 'JSXFragment') {
72
+ return false;
73
+ }
74
+ var nameString = stringifyJsxTagName(currentJsxNode.openingElement.name);
75
+ var attributeName = typeof node.name.name === 'string'
76
+ ? node.name.name
77
+ : node.name.name.name;
78
+ // match exclude
79
+ for (var _i = 0, propExcludePattern_1 = propExcludePattern; _i < propExcludePattern_1.length; _i++) {
80
+ var _a = propExcludePattern_1[_i], tagNamePattern = _a[0], propNamePattern = _a[1];
81
+ if (tagNamePattern.test(nameString) &&
82
+ propNamePattern.test(attributeName)) {
83
+ return true;
84
+ }
85
+ }
86
+ // match include
87
+ for (var _b = 0, propIncludePattern_1 = propIncludePattern; _b < propIncludePattern_1.length; _b++) {
88
+ var _c = propIncludePattern_1[_b], tagNamePattern = _c[0], propNamePattern = _c[1];
89
+ if (tagNamePattern.test(nameString) &&
90
+ propNamePattern.test(attributeName)) {
91
+ return false;
92
+ }
93
+ }
94
+ return true;
95
+ };
96
+ var checkJSXExpression = function (node) {
97
+ // Check if this is either a string literal / template literal, or the concat of them.
98
+ if ((node.type === 'Literal' && typeof node.value === 'string') ||
99
+ node.type === 'TemplateLiteral') {
100
+ context.report({
101
+ node: node,
102
+ message: 'Cannot have untranslated text in JSX',
103
+ });
104
+ }
105
+ else if (node.type === 'BinaryExpression' && node.operator === '+') {
106
+ checkJSXExpression(node.left);
107
+ checkJSXExpression(node.right);
108
+ }
109
+ };
110
+ return {
111
+ JSXElement: function (node) {
112
+ lexicalJsxStack.push(node);
113
+ },
114
+ 'JSXElement:exit': function () {
115
+ lexicalJsxStack.pop();
116
+ },
117
+ JSXFragment: function (node) {
118
+ lexicalJsxStack.push(node);
119
+ },
120
+ 'JSXFragment:exit': function () {
121
+ lexicalJsxStack.pop();
122
+ },
123
+ JSXAttribute: function (node) {
124
+ if (shouldSkipCurrentJsxAttribute(node)) {
125
+ return;
126
+ }
127
+ if (!node.value) {
128
+ return;
129
+ }
130
+ if (node.value.type === 'Literal') {
131
+ context.report({
132
+ node: node,
133
+ message: 'Cannot have untranslated text in JSX',
134
+ });
135
+ }
136
+ else if (node.value.type === 'JSXExpressionContainer' &&
137
+ node.value.expression.type !== 'JSXEmptyExpression') {
138
+ checkJSXExpression(node.value.expression);
139
+ }
140
+ },
141
+ JSXText: function (node) {
142
+ // Ignore purely spacing fragments
143
+ if (!node.value.replace(/\s*/gm, '')) {
144
+ return;
145
+ }
146
+ context.report({
147
+ node: node,
148
+ message: 'Cannot have untranslated text in JSX',
149
+ });
150
+ },
151
+ // Children expression container
152
+ 'JSXElement > JSXExpressionContainer': function (node) {
153
+ if (node.expression.type !== 'JSXEmptyExpression') {
154
+ checkJSXExpression(node.expression);
155
+ }
156
+ },
157
+ };
158
+ },
159
+ };
160
+ exports.default = rule;
@@ -1 +1 @@
1
- {"version":3,"file":"no-multiple-whitespaces.d.ts","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-formatjs/rules/no-multiple-whitespaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAmC3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA8BhB,CAAA;AAED,eAAe,IAAI,CAAA"}
1
+ {"version":3,"file":"no-multiple-whitespaces.d.ts","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-formatjs/rules/no-multiple-whitespaces.ts"],"names":[],"mappings":"AAOA,OAAO,EAAC,IAAI,EAAC,MAAM,QAAQ,CAAA;AAyJ3B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA+BhB,CAAA;AAED,eAAe,IAAI,CAAA"}
@@ -1,21 +1,121 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ var icu_messageformat_parser_1 = require("@formatjs/icu-messageformat-parser");
3
4
  var util_1 = require("../util");
4
- var MULTIPLE_SPACES = /\s{2,}/g;
5
+ function isAstValid(ast) {
6
+ for (var _i = 0, ast_1 = ast; _i < ast_1.length; _i++) {
7
+ var element = ast_1[_i];
8
+ switch (element.type) {
9
+ case icu_messageformat_parser_1.TYPE.literal:
10
+ if (/\s{2,}/gm.test(element.value)) {
11
+ return false;
12
+ }
13
+ break;
14
+ case icu_messageformat_parser_1.TYPE.argument:
15
+ case icu_messageformat_parser_1.TYPE.date:
16
+ case icu_messageformat_parser_1.TYPE.literal:
17
+ case icu_messageformat_parser_1.TYPE.number:
18
+ case icu_messageformat_parser_1.TYPE.pound:
19
+ case icu_messageformat_parser_1.TYPE.tag:
20
+ case icu_messageformat_parser_1.TYPE.time:
21
+ break;
22
+ case icu_messageformat_parser_1.TYPE.plural:
23
+ case icu_messageformat_parser_1.TYPE.select: {
24
+ for (var _a = 0, _b = Object.values(element.options); _a < _b.length; _a++) {
25
+ var option = _b[_a];
26
+ if (!isAstValid(option.value)) {
27
+ return false;
28
+ }
29
+ }
30
+ break;
31
+ }
32
+ }
33
+ }
34
+ return true;
35
+ }
36
+ function trimMultiWhitespaces(message, ast) {
37
+ var literalElements = [];
38
+ var collectLiteralElements = function (elements) {
39
+ for (var _i = 0, elements_1 = elements; _i < elements_1.length; _i++) {
40
+ var element = elements_1[_i];
41
+ switch (element.type) {
42
+ case icu_messageformat_parser_1.TYPE.literal:
43
+ literalElements.push(element);
44
+ break;
45
+ case icu_messageformat_parser_1.TYPE.argument:
46
+ case icu_messageformat_parser_1.TYPE.date:
47
+ case icu_messageformat_parser_1.TYPE.literal:
48
+ case icu_messageformat_parser_1.TYPE.number:
49
+ case icu_messageformat_parser_1.TYPE.pound:
50
+ case icu_messageformat_parser_1.TYPE.tag:
51
+ case icu_messageformat_parser_1.TYPE.time:
52
+ break;
53
+ case icu_messageformat_parser_1.TYPE.plural:
54
+ case icu_messageformat_parser_1.TYPE.select: {
55
+ for (var _a = 0, _b = Object.values(element.options); _a < _b.length; _a++) {
56
+ var option = _b[_a];
57
+ collectLiteralElements(option.value);
58
+ }
59
+ break;
60
+ }
61
+ }
62
+ }
63
+ };
64
+ collectLiteralElements(ast);
65
+ // Surgically trim whitespaces in the literal element ranges.
66
+ // This is to preserve the original whitespaces and newlines info that are lost to parsing.
67
+ var trimmedFragments = [];
68
+ var currentOffset = 0;
69
+ for (var _i = 0, literalElements_1 = literalElements; _i < literalElements_1.length; _i++) {
70
+ var literal = literalElements_1[_i];
71
+ var _a = literal.location, start = _a.start, end = _a.end;
72
+ var startOffset = start.offset;
73
+ var endOffset = end.offset;
74
+ trimmedFragments.push(message.slice(currentOffset, startOffset));
75
+ trimmedFragments.push(message.slice(startOffset, endOffset).replace(/\s{2,}/gm, ' '));
76
+ currentOffset = endOffset;
77
+ }
78
+ trimmedFragments.push(message.slice(currentOffset));
79
+ return trimmedFragments.join('');
80
+ }
5
81
  function checkNode(context, node) {
6
82
  var msgs = (0, util_1.extractMessages)(node, context.settings);
7
83
  var _loop_1 = function (defaultMessage, messageNode) {
8
84
  if (!defaultMessage || !messageNode) {
9
85
  return "continue";
10
86
  }
11
- if (MULTIPLE_SPACES.test(defaultMessage)) {
87
+ var ast;
88
+ try {
89
+ ast = (0, icu_messageformat_parser_1.parse)(defaultMessage, { captureLocation: true });
90
+ }
91
+ catch (e) {
92
+ context.report({
93
+ node: messageNode,
94
+ message: e instanceof Error ? e.message : String(e),
95
+ });
96
+ return { value: void 0 };
97
+ }
98
+ if (!isAstValid(ast)) {
12
99
  var reportObject = {
13
100
  node: messageNode,
14
101
  message: 'Multiple consecutive whitespaces are not allowed',
15
102
  };
16
- if (messageNode.type === 'Literal' && messageNode.raw) {
103
+ if (messageNode.type === 'Literal' &&
104
+ messageNode.value &&
105
+ typeof messageNode.value === 'string') {
106
+ reportObject.fix = function (fixer) {
107
+ return fixer.replaceText(messageNode, JSON.stringify(trimMultiWhitespaces(messageNode.value, ast)));
108
+ };
109
+ }
110
+ else if (messageNode.type === 'TemplateLiteral' &&
111
+ messageNode.quasis.length === 1 &&
112
+ messageNode.expressions.length === 0) {
17
113
  reportObject.fix = function (fixer) {
18
- return fixer.replaceText(messageNode, messageNode.raw.replace(MULTIPLE_SPACES, ' '));
114
+ return fixer.replaceText(messageNode, '`' +
115
+ trimMultiWhitespaces(messageNode.quasis[0].value.cooked, ast)
116
+ .replace(/\\/g, '\\\\')
117
+ .replace(/`/g, '\\`') +
118
+ '`');
19
119
  };
20
120
  }
21
121
  context.report(reportObject);
@@ -23,19 +123,21 @@ function checkNode(context, node) {
23
123
  };
24
124
  for (var _i = 0, msgs_1 = msgs; _i < msgs_1.length; _i++) {
25
125
  var _a = msgs_1[_i][0], defaultMessage = _a.message.defaultMessage, messageNode = _a.messageNode;
26
- _loop_1(defaultMessage, messageNode);
126
+ var state_1 = _loop_1(defaultMessage, messageNode);
127
+ if (typeof state_1 === "object")
128
+ return state_1.value;
27
129
  }
28
130
  }
29
131
  var rule = {
30
132
  meta: {
31
133
  type: 'problem',
32
134
  docs: {
33
- description: 'Disallow emojis in message',
135
+ description: 'Prevents usage of multiple consecutive whitespaces in message',
34
136
  category: 'Errors',
35
137
  recommended: false,
36
- url: 'https://formatjs.io/docs/tooling/linter#no-emoji',
138
+ url: 'https://formatjs.io/docs/tooling/linter#no-multiple-whitespaces',
37
139
  },
38
- fixable: 'whitespace',
140
+ fixable: 'code',
39
141
  },
40
142
  create: function (context) {
41
143
  var callExpressionVisitor = function (node) {
package/util.d.ts CHANGED
@@ -2,7 +2,7 @@ import { TSESTree } from '@typescript-eslint/typescript-estree';
2
2
  export interface MessageDescriptor {
3
3
  id?: string;
4
4
  defaultMessage?: string;
5
- description?: string;
5
+ description?: string | object;
6
6
  }
7
7
  export interface Settings {
8
8
  excludeMessageDeclCalls?: boolean;
package/util.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../../../packages/eslint-plugin-formatjs/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,sCAAsC,CAAA;AAE7D,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAMD,MAAM,WAAW,QAAQ;IACvB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAA;IAClC,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAA;IACnC,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AACD,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,iBAAiB,CAAA;IAC1B,WAAW,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IACzE,eAAe,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAA;IAC3D,eAAe,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IAC7E,WAAW,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IACzE,UAAU,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAA;CACvD;AA4ND,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,IAAI,EACnB,EACE,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,GACxB,GAAE,QAAa,GACf,KAAK,CAAC,CAAC,yBAAyB,EAAE,QAAQ,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC,CAiDrE"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../../../packages/eslint-plugin-formatjs/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,sCAAsC,CAAA;AAE7D,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC9B;AAMD,MAAM,WAAW,QAAQ;IACvB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAA;IAClC,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAA;IACnC,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AACD,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,iBAAiB,CAAA;IAC1B,WAAW,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IACzE,eAAe,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAA;IAC3D,eAAe,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IAC7E,WAAW,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IACzE,UAAU,CAAC,EAAE,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAA;CACvD;AA4ND,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,IAAI,EACnB,EACE,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,GACxB,GAAE,QAAa,GACf,KAAK,CAAC,CAAC,yBAAyB,EAAE,QAAQ,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC,CAiDrE"}