eslint-plugin-crisp 1.5.12 → 1.6.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.
Files changed (89) hide show
  1. package/.github/workflows/build.yml +6 -0
  2. package/README.md +2 -1
  3. package/index.js +2 -0
  4. package/package.json +16 -7
  5. package/recommended-ts.js +1 -1
  6. package/recommended-vue.js +3 -2
  7. package/rules/align-comments.js +6 -3
  8. package/rules/constructor-variables.js +17 -2
  9. package/rules/header-check.js +8 -2
  10. package/rules/header-comments-check.js +14 -3
  11. package/rules/import-group-comment.js +13 -4
  12. package/rules/jsdoc-enforce-access.js +16 -10
  13. package/rules/no-extra-line-within-function.js +8 -5
  14. package/rules/no-short-parameters.js +1 -1
  15. package/rules/no-snake-case.js +0 -24
  16. package/rules/no-space-in-optional-arguments.js +5 -2
  17. package/rules/one-space-after-operator.js +59 -14
  18. package/rules/vue-header-check.js +4 -3
  19. package/rules/vue-no-regex-data.js +1 -2
  20. package/rules/vue-pinia-action-single-parameter.js +158 -0
  21. package/rules/vue-props-declaration-line-break.js +8 -3
  22. package/rules/vue-watch-handler.js +34 -1
  23. package/tests/fixtures/vue-header-check/missing-script-style/TestComponent.vue +19 -0
  24. package/tests/fixtures/vue-header-check/missing-template/TestComponent.vue +3 -0
  25. package/tests/fixtures/vue-header-check/valid/TestComponent.vue +27 -0
  26. package/tests/rule-tester.js +78 -0
  27. package/tests/rules/align-comments.test.js +25 -0
  28. package/tests/rules/align-consecutive-class-assignements.test.js +24 -0
  29. package/tests/rules/align-one-var.test.js +31 -0
  30. package/tests/rules/align-requires.test.js +27 -0
  31. package/tests/rules/arrow-block-newline.test.js +23 -0
  32. package/tests/rules/consecutive-line-comments.test.js +27 -0
  33. package/tests/rules/const.test.js +52 -0
  34. package/tests/rules/constructor-variables.test.js +52 -0
  35. package/tests/rules/enforce-optional.test.js +24 -0
  36. package/tests/rules/header-check.test.js +69 -0
  37. package/tests/rules/header-comments-check.test.js +90 -0
  38. package/tests/rules/import-group-comment.test.js +88 -0
  39. package/tests/rules/import-group-order.test.js +58 -0
  40. package/tests/rules/jsdoc-align-params.test.js +32 -0
  41. package/tests/rules/jsdoc-check-indentation.test.js +45 -0
  42. package/tests/rules/jsdoc-check-optional-params.test.js +40 -0
  43. package/tests/rules/jsdoc-description-length.test.js +60 -0
  44. package/tests/rules/jsdoc-description-multiline.test.js +34 -0
  45. package/tests/rules/jsdoc-enforce-access.test.js +66 -0
  46. package/tests/rules/jsdoc-enforce-classdesc.test.js +39 -0
  47. package/tests/rules/jsdoc-no-param-return.test.js +47 -0
  48. package/tests/rules/jsdoc-require-description-uppercase.test.js +52 -0
  49. package/tests/rules/methods-naming.test.js +70 -0
  50. package/tests/rules/methods-ordering.test.js +75 -0
  51. package/tests/rules/multiline-comment-end-backslash.test.js +52 -0
  52. package/tests/rules/newline-after-switch-case.test.js +75 -0
  53. package/tests/rules/no-async.test.js +57 -0
  54. package/tests/rules/no-extra-line-within-function.test.js +87 -0
  55. package/tests/rules/no-inline-function-in-ternary.test.js +59 -0
  56. package/tests/rules/no-short-parameters.test.js +62 -0
  57. package/tests/rules/no-snake-case.test.js +81 -0
  58. package/tests/rules/no-space-in-optional-arguments.test.js +40 -0
  59. package/tests/rules/no-useless-template-literals.test.js +36 -0
  60. package/tests/rules/no-var-in-blocks.test.js +40 -0
  61. package/tests/rules/one-space-after-operator.test.js +69 -0
  62. package/tests/rules/regex-in-constructor.test.js +40 -0
  63. package/tests/rules/ternary-parenthesis.test.js +34 -0
  64. package/tests/rules/two-lines-between-class-members.test.js +62 -0
  65. package/tests/rules/variable-names.test.js +60 -0
  66. package/tests/rules/vue-attribute-comma.test.js +43 -0
  67. package/tests/rules/vue-attribute-linebreak.test.js +54 -0
  68. package/tests/rules/vue-class-attribute-last.test.js +59 -0
  69. package/tests/rules/vue-class-shorthand-max-len.test.js +67 -0
  70. package/tests/rules/vue-computed-order.test.js +27 -0
  71. package/tests/rules/vue-data-comment.test.js +61 -0
  72. package/tests/rules/vue-define-component.test.js +45 -0
  73. package/tests/rules/vue-emits-order.test.js +27 -0
  74. package/tests/rules/vue-header-check.test.js +49 -0
  75. package/tests/rules/vue-html-indent.test.js +77 -0
  76. package/tests/rules/vue-html-quotes.test.js +71 -0
  77. package/tests/rules/vue-methods-separator.test.js +55 -0
  78. package/tests/rules/vue-name-prop.test.js +59 -0
  79. package/tests/rules/vue-no-component-class-shorthand.test.js +49 -0
  80. package/tests/rules/vue-no-mixed-class.test.js +56 -0
  81. package/tests/rules/vue-no-name-option.test.js +44 -0
  82. package/tests/rules/vue-no-regex-data.test.js +39 -0
  83. package/tests/rules/vue-pinia-action-single-parameter.test.js +234 -0
  84. package/tests/rules/vue-props-declaration-line-break.test.js +68 -0
  85. package/tests/rules/vue-props-declaration-multiline.test.js +40 -0
  86. package/tests/rules/vue-props-declaration-order.test.js +27 -0
  87. package/tests/rules/vue-ref-case.test.js +40 -0
  88. package/tests/rules/vue-watch-handler.test.js +89 -0
  89. package/tests/rules-coverage.test.js +19 -0
@@ -26,5 +26,11 @@ jobs:
26
26
  - name: Verify versions
27
27
  run: node --version && npm --version && node -p process.versions.v8
28
28
 
29
+ - name: Install dependencies
30
+ run: npm ci
31
+
32
+ - name: Run tests
33
+ run: npm test
34
+
29
35
  - name: Release package
30
36
  run: npm publish --ignore-scripts --provenance
package/README.md CHANGED
@@ -133,12 +133,13 @@ Each item has emojis denoting:
133
133
  | [crisp/multiline-comment-end-backslash](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/multiline-comment-end-backslash.js) | Enforces multiline comments to end with a backslash | 🟠 | 🟢 | 🟣 |
134
134
  | [crisp/newline-after-switch-case](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/newline-after-switch-case.js) | Requires newline between switch cases | 🟠 | 🟢 | 🟣 |
135
135
  | [crisp/no-async](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/no-async.js) | Disallows the use of `async/syntax`, in favor of `Promise` | 🟠 | 🟢 |
136
- | [crisp/no-short-parameters](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/no-short-parameters.js) | Disallow parameter names shorter than 3 characters | 🟠 | 🟢 | 🟣 |
136
+ | [crisp/no-short-parameters](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/no-short-parameters.js) | Disallows one-character parameter names | 🟠 | 🟢 | 🟣 |
137
137
  | [crisp/no-space-in-optional-arguments](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/no-space-in-optional-arguments.js) | Disallows space before or after `=` in optional parameters | 🟠 |
138
138
  | [crisp/no-snake-case](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/no-snake-case.js) | Disallows snake_case | | 🟢 | 🟣 |
139
139
  | [crisp/no-useless-template-literals](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/no-useless-template-literals.js) | Disallows unnecessary use of template literals | | 🟢 | 🟣 |
140
140
  | [crisp/no-var-in-blocks](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/no-var-in-blocks.js) | Disallows `var` declarations inside function, method, or class block | 🟠 | 🟢 | 🟣 |
141
141
  | [crisp/one-space-after-operator](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/one-space-after-operator.js) | Enforces at least one space before and one space after `=` and `:` operators | 🟠 | 🟢 | 🟣 |
142
+ | [crisp/vue-pinia-action-single-parameter](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/vue-pinia-action-single-parameter.js) | Requires Pinia actions to accept at most one payload argument | | 🟢 | |
142
143
  | [crisp/regex-in-constructor](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/regex-in-constructor.js) | Ensures regular expressions are defined in the class constructor | 🟠 | 🟢 | 🟣 |
143
144
  | [crisp/ternary-parenthesis](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/ternary-parenthesis.js) | Enforces parentheses around conditions with operators in ternary expressions | 🟠 | 🟢 | 🟣 |
144
145
  | [crisp/two-lines-between-class-members](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/two-lines-between-class-members.js) | Requires exactly two line breaks between class methods | 🟠 |
package/index.js CHANGED
@@ -58,6 +58,7 @@ import ruleVueNoComponentClassShorthand from "./rules/vue-no-component-class-sho
58
58
  import ruleVueNoMixedClass from "./rules/vue-no-mixed-class.js";
59
59
  import ruleVueNoNameOption from "./rules/vue-no-name-option.js";
60
60
  import ruleVueNoRegexData from "./rules/vue-no-regex-data.js";
61
+ import ruleVuePiniaActionSingleParameter from "./rules/vue-pinia-action-single-parameter.js";
61
62
  import ruleVuePropsDeclarationLineBreak from "./rules/vue-props-declaration-line-break.js";
62
63
  import ruleVuePropsDeclarationMultiline from "./rules/vue-props-declaration-multiline.js";
63
64
  import ruleVuePropsDeclarationOrder from "./rules/vue-props-declaration-order.js";
@@ -134,6 +135,7 @@ const plugin = {
134
135
  "vue-no-mixed-class": ruleVueNoMixedClass,
135
136
  "vue-no-name-option": ruleVueNoNameOption,
136
137
  "vue-no-regex-data": ruleVueNoRegexData,
138
+ "vue-pinia-action-single-parameter": ruleVuePiniaActionSingleParameter,
137
139
  "vue-props-declaration-line-break": ruleVuePropsDeclarationLineBreak,
138
140
  "vue-props-declaration-multiline": ruleVuePropsDeclarationMultiline,
139
141
  "vue-props-declaration-order": ruleVuePropsDeclarationOrder,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-crisp",
3
- "version": "1.5.12",
3
+ "version": "1.6.0",
4
4
  "description": "Custom ESLint Rules for Crisp",
5
5
  "author": "Crisp IM SAS",
6
6
  "main": "index.js",
@@ -13,17 +13,26 @@
13
13
  "bugs": {
14
14
  "url": "https://github.com/crisp-oss/eslint-plugin-crisp/issues"
15
15
  },
16
+ "scripts": {
17
+ "test": "node --test"
18
+ },
16
19
  "dependencies": {
20
+ "@eslint/js": "9.39.5",
17
21
  "doctrine": "3.0.0",
18
- "lodash-es": "4.17.21",
19
22
  "eslint-plugin-jsdoc": "50.8.0",
20
- "typescript": "5.9.2",
21
- "typescript-eslint": "8.41.0"
23
+ "lodash-es": "4.18.1",
24
+ "typescript": "5.9.3",
25
+ "typescript-eslint": "8.72.0"
26
+ },
27
+ "devDependencies": {
28
+ "eslint": "9.39.5",
29
+ "vue-eslint-parser": "10.4.1",
30
+ "vue-eslint-parser-template-tokenizer-pug": "1.0.0"
22
31
  },
23
32
  "peerDependencies": {
24
- "eslint": "9.39.1",
25
- "eslint-plugin-vue": "10.8.0",
26
- "eslint-plugin-vue-pug": "1.0.0-alpha.5",
33
+ "eslint": "^9.39.5",
34
+ "eslint-plugin-vue": "^10.11.1",
35
+ "eslint-plugin-vue-pug": "1.0.0-alpha.6",
27
36
  "globals": "15.15.0"
28
37
  },
29
38
  "keywords": [
package/recommended-ts.js CHANGED
@@ -4,7 +4,7 @@ import pluginTS from "typescript-eslint";
4
4
 
5
5
  export default function configRecommendedTS(pluginCrisp) {
6
6
  return [
7
- pluginTS.configs.recommendedTypeChecked,
7
+ ...pluginTS.configs.recommendedTypeChecked,
8
8
  pluginJSDoc.configs["flat/recommended"],
9
9
 
10
10
  {
@@ -8,8 +8,8 @@ export default function configRecommendedVue(pluginCrisp) {
8
8
  return [
9
9
  pluginJS.configs.recommended,
10
10
  pluginJSDoc.configs["flat/recommended"],
11
- pluginVue.configs["flat/recommended"],
12
- pluginVuePug.configs["flat/recommended"],
11
+ ...pluginVue.configs["flat/recommended"],
12
+ ...pluginVuePug.configs["flat/recommended"],
13
13
 
14
14
  {
15
15
  files: [
@@ -393,6 +393,7 @@ export default function configRecommendedVue(pluginCrisp) {
393
393
  "crisp/vue-no-mixed-class": "error",
394
394
  "crisp/vue-no-name-option": "error",
395
395
  "crisp/vue-no-regex-data": "error",
396
+ "crisp/vue-pinia-action-single-parameter": "error",
396
397
  "crisp/vue-props-declaration-line-break": "error",
397
398
  "crisp/vue-props-declaration-multiline": "error",
398
399
  "crisp/vue-props-declaration-order": "error",
@@ -52,9 +52,12 @@ export default {
52
52
 
53
53
  fix(fixer) {
54
54
  const indent = " ".repeat(nodeIndent);
55
- const commentText = sourceCode.getText(comment);
56
- const fixedComment = `\n${indent}${commentText.trim()}`;
57
- return fixer.replaceTextRange([comment.range[0], comment.range[1]], fixedComment);
55
+ const lineStart = sourceCode.getIndexFromLoc({
56
+ line: comment.loc.start.line,
57
+ column: 0
58
+ });
59
+
60
+ return fixer.replaceTextRange([lineStart, comment.range[0]], indent);
58
61
  },
59
62
  });
60
63
  }
@@ -25,7 +25,7 @@ export default {
25
25
 
26
26
  create(context) {
27
27
  const options = context.options[0] || {};
28
- const filename = context.getFilename();
28
+ const filename = context.filename || context.getFilename();
29
29
  const filenameExceptions = options.filenameExceptions || [];
30
30
  const variableExceptions = options.variableExceptions || [];
31
31
 
@@ -34,7 +34,22 @@ export default {
34
34
  return {
35
35
  "MethodDefinition[kind='constructor'] > FunctionExpression > BlockStatement > ExpressionStatement > AssignmentExpression": function(node) {
36
36
  if(node.left.type === "MemberExpression" && node.left.object.type === "ThisExpression") {
37
- const varName = node.left.property.name;
37
+ let varName = null;
38
+
39
+ if (!node.left.computed && node.left.property.type === "Identifier") {
40
+ varName = node.left.property.name;
41
+ } else if (
42
+ node.left.computed &&
43
+ node.left.property.type === "Literal" &&
44
+ typeof node.left.property.value === "string"
45
+ ) {
46
+ varName = node.left.property.value;
47
+ }
48
+
49
+ if (!varName) {
50
+ return;
51
+ }
52
+
38
53
  if (!varName.startsWith("_") && !isFilenameException && !variableExceptions.includes(varName)) {
39
54
  context.report({
40
55
  node,
@@ -55,9 +55,15 @@ export default {
55
55
 
56
56
  if (headerStart && headerEnd) {
57
57
  // Construct the final headerFormat RegExps
58
- const headerFormats = basePatterns.map(pattern => headerStart && headerEnd ? new RegExp("^" + headerStart + pattern + headerEnd) : null);
58
+ const headerFormats = basePatterns.flatMap(pattern => {
59
+ try {
60
+ return [new RegExp("^" + headerStart + pattern + headerEnd)];
61
+ } catch {
62
+ return [];
63
+ }
64
+ });
59
65
 
60
- if (headerFormats.some(format => format && format.test(fileContent)) === false) {
66
+ if (headerFormats.some(format => format.test(fileContent)) === false) {
61
67
  context.report({
62
68
  node,
63
69
  message: "File must start with the proper header.",
@@ -113,7 +113,7 @@ export default {
113
113
  },
114
114
 
115
115
  create(context) {
116
- const filename = context.getFilename();
116
+ const filename = context.filename || context.getFilename();
117
117
 
118
118
  if (!filename.endsWith(".js") && !filename.endsWith(".ts")) {
119
119
  return {};
@@ -223,7 +223,10 @@ export default {
223
223
 
224
224
  case "ExportDefaultDeclaration": {
225
225
  // Only enforce the 'EXPORTS' comment block if it's the last statement in the file
226
- if (startNode !== context.getSourceCode().ast.body.slice(-1)[0] && (!comment || `/*${comment.value.trim()}*/` !== COMMENT_HEADER_FORMAT("EXPORTS"))) {
226
+ if (
227
+ startNode === sourceCode.ast.body.slice(-1)[0] &&
228
+ (!comment || `/*${comment.value.trim()}*/` !== COMMENT_HEADER_FORMAT("EXPORTS"))
229
+ ) {
227
230
  context.report({
228
231
  node: startNode,
229
232
  message: "Export group must be preceded by a 'EXPORTS' comment block",
@@ -237,9 +240,17 @@ export default {
237
240
 
238
241
  return {
239
242
  ":statement": (node) => {
243
+ if (node.parent.type !== "Program") {
244
+ return;
245
+ }
246
+
240
247
  // If the type of node has changed since last time, or if we have a \
241
248
  // regex declaration and the previous declaration wasn't one
242
- if (node.type !== lastNode || isRegex(node) !== isRegex(lastNode)) {
249
+ if (
250
+ !lastNode ||
251
+ node.type !== lastNode.type ||
252
+ isRegex(node) !== isRegex(lastNode)
253
+ ) {
243
254
  // Check the group starting with the last node
244
255
  if (groupStart) {
245
256
  checkGroup(lastNode.type, groupStart);
@@ -29,9 +29,14 @@ function isIgnoreComment(comment) {
29
29
  }
30
30
 
31
31
  function getDirectoryName(filePath) {
32
- const pathParts = filePath.split("/");
32
+ if (!filePath || filePath.startsWith("<")) {
33
+ return "MAIN";
34
+ }
35
+
36
+ const pathParts = filePath.replace(/\\/g, "/").split("/").filter(Boolean);
37
+ const directoryName = pathParts.at(-2);
33
38
 
34
- return pathParts[pathParts.length - 2].toUpperCase();
39
+ return directoryName ? directoryName.toUpperCase() : "MAIN";
35
40
  }
36
41
 
37
42
  function generateExpectedComment(group) {
@@ -70,8 +75,12 @@ export default {
70
75
  const customGroups = context.options[0] || {}; // Get custom groups from options
71
76
 
72
77
  // Pre-compile custom regexes once instead of on every import
73
- const compiledCustomGroups = Object.entries(customGroups).map(([regexStr, group]) => {
74
- return [new RegExp(regexStr), group];
78
+ const compiledCustomGroups = Object.entries(customGroups).flatMap(([regexStr, group]) => {
79
+ try {
80
+ return [[new RegExp(regexStr), group]];
81
+ } catch {
82
+ return [];
83
+ }
75
84
  });
76
85
 
77
86
  let currentGroupComment = null;
@@ -1,5 +1,5 @@
1
1
  // Pre-compiled regex for access modifier check
2
- const ACCESS_MODIFIER_PATTERN = /@(public|protected|private|internal)/;
2
+ const ACCESS_MODIFIER_PATTERN = /@(public|protected|private|internal)(?=\s|\*|$)/;
3
3
 
4
4
  export default {
5
5
  meta: {
@@ -16,19 +16,25 @@ export default {
16
16
 
17
17
  function checkNodeForJSDoc(node) {
18
18
  const comments = sourceCode.getCommentsBefore(node);
19
+ let jsdocComment = null;
19
20
 
20
- if (comments.length > 0) {
21
- const lastComment = comments[comments.length - 1];
21
+ for (let i = comments.length - 1; i >= 0; i--) {
22
+ if (
23
+ comments[i].type === "Block" &&
24
+ comments[i].value.startsWith("*")
25
+ ) {
26
+ jsdocComment = comments[i];
22
27
 
23
- if (lastComment.type === "Block" && lastComment.value.startsWith("*")) {
24
- if (!ACCESS_MODIFIER_PATTERN.test(lastComment.value)) {
25
- context.report({
26
- node,
27
- message: "JSDoc comment should contain @public, @protected, @private or @internal",
28
- });
29
- }
28
+ break;
30
29
  }
31
30
  }
31
+
32
+ if (jsdocComment && !ACCESS_MODIFIER_PATTERN.test(jsdocComment.value)) {
33
+ context.report({
34
+ node,
35
+ message: "JSDoc comment should contain @public, @protected, @private or @internal",
36
+ });
37
+ }
32
38
  }
33
39
 
34
40
  return {
@@ -37,11 +37,14 @@ export default {
37
37
  node: currentNode,
38
38
  message: "Expected at most one line break between declarations within a function.",
39
39
  fix(fixer) {
40
- const range = [
41
- sourceCode.getIndexFromLoc({ line: lineOfPreviousNodeEnd, column: 0 }),
42
- sourceCode.getIndexFromLoc({ line: lineOfCurrentNode, column: 0 })
43
- ];
44
- return fixer.replaceTextRange(range, "\n\n");
40
+ const range = [previousNode.range[1], currentNode.range[0]];
41
+ const textBetween = sourceCode.text.slice(...range);
42
+ const fixedText = textBetween.replace(
43
+ /\n(?:[ \t]*\n){2,}/g,
44
+ "\n\n"
45
+ );
46
+
47
+ return fixer.replaceTextRange(range, fixedText);
45
48
  }
46
49
  });
47
50
  }
@@ -2,7 +2,7 @@ export default {
2
2
  meta: {
3
3
  type: "suggestion",
4
4
  docs: {
5
- description: "disallow short parameter names",
5
+ description: "disallow one-character parameter names",
6
6
  category: "Stylistic Issues",
7
7
  },
8
8
  schema: [
@@ -92,18 +92,6 @@ export default {
92
92
  reportSnakeCase(node, name, "Method");
93
93
  }
94
94
  }
95
-
96
- // Check method parameters
97
- if (node.value && node.value.params) {
98
- node.value.params.forEach((param) => {
99
- if (param.type === "Identifier" && param.name) {
100
- const name = param.name;
101
- if (isSnakeCase(name)) {
102
- reportSnakeCase(param, name, "Parameter");
103
- }
104
- }
105
- });
106
- }
107
95
  },
108
96
 
109
97
  // Check function expressions
@@ -142,18 +130,6 @@ export default {
142
130
  reportSnakeCase(node, name, "Method");
143
131
  }
144
132
  }
145
-
146
- // Check method parameters
147
- if (node.value && node.value.params) {
148
- node.value.params.forEach((param) => {
149
- if (param.type === "Identifier" && param.name) {
150
- const name = param.name;
151
- if (isSnakeCase(name)) {
152
- reportSnakeCase(param, name, "Parameter");
153
- }
154
- }
155
- });
156
- }
157
133
  }
158
134
  }
159
135
  };
@@ -4,10 +4,13 @@ export default {
4
4
  "FunctionDeclaration, FunctionExpression, ArrowFunctionExpression, MethodDefinition": function(node) {
5
5
  (node.params || []).forEach(param => {
6
6
  if (param.type === "AssignmentPattern") {
7
- const sourceCode = context.getSourceCode();
7
+ const sourceCode = context.sourceCode || context.getSourceCode();
8
8
  const operatorToken = sourceCode.getFirstTokenBetween(param.left, param.right, token => token.value === "=");
9
9
 
10
- if (sourceCode.getTokenBefore(operatorToken).end !== operatorToken.start || sourceCode.getTokenAfter(operatorToken).start !== operatorToken.end) {
10
+ if (
11
+ sourceCode.getTokenBefore(operatorToken).range[1] !== operatorToken.range[0] ||
12
+ sourceCode.getTokenAfter(operatorToken).range[0] !== operatorToken.range[1]
13
+ ) {
11
14
  context.report({
12
15
  node,
13
16
  message: "There should be no space before or after = in optional parameters"
@@ -27,17 +27,47 @@ export default {
27
27
  const config = context.options[0] || {};
28
28
  const checkColon = config.checkColon !== false;
29
29
 
30
- const checkSpacing = (node, operatorToken, operatorName) => {
31
- if (Math.abs(sourceCode.getTokenBefore(operatorToken).loc.end.column - operatorToken.loc.start.column) < 1 || sourceCode.getTokenAfter(operatorToken).loc.start.column - operatorToken.loc.end.column > 1) {
30
+ const checkSpacing = (operatorToken) => {
31
+ if (!operatorToken) {
32
+ return;
33
+ }
34
+
35
+ const tokenBefore = sourceCode.getTokenBefore(operatorToken);
36
+ const tokenAfter = sourceCode.getTokenAfter(operatorToken);
37
+ const beforeText = sourceCode.text.slice(
38
+ tokenBefore.range[1],
39
+ operatorToken.range[0]
40
+ );
41
+ const afterText = sourceCode.text.slice(
42
+ operatorToken.range[1],
43
+ tokenAfter.range[0]
44
+ );
45
+ const hasSpaceBefore = /[ \t]$/.test(beforeText);
46
+ const hasOneSpaceAfter = afterText === " ";
47
+
48
+ if (!hasSpaceBefore || !hasOneSpaceAfter) {
32
49
  context.report({
33
50
  node: operatorToken,
34
- message: `There should be at least one space before and exactly one space after '${operatorName}'`,
51
+ message: `There should be at least one space before and exactly one space after '${operatorToken.value}'`,
35
52
 
36
53
  fix(fixer) {
37
- return fixer.replaceTextRange(
38
- [sourceCode.getTokenBefore(operatorToken).range[1], sourceCode.getTokenAfter(operatorToken).range[0]],
39
- ` ${operatorName} `
40
- );
54
+ const fixes = [];
55
+
56
+ if (!hasSpaceBefore && /^[ \t]*$/.test(beforeText)) {
57
+ fixes.push(fixer.replaceTextRange(
58
+ [tokenBefore.range[1], operatorToken.range[0]],
59
+ " "
60
+ ));
61
+ }
62
+
63
+ if (!hasOneSpaceAfter && /^[ \t]*$/.test(afterText)) {
64
+ fixes.push(fixer.replaceTextRange(
65
+ [operatorToken.range[1], tokenAfter.range[0]],
66
+ " "
67
+ ));
68
+ }
69
+
70
+ return fixes.length ? fixes : null;
41
71
  }
42
72
  });
43
73
  }
@@ -45,20 +75,35 @@ export default {
45
75
 
46
76
  return {
47
77
  AssignmentExpression(node) {
48
- const operatorToken = sourceCode.getTokenBefore(node.right);
49
- checkSpacing(node, operatorToken, "=");
78
+ const operatorToken = sourceCode.getFirstTokenBetween(
79
+ node.left,
80
+ node.right,
81
+ token => token.value === node.operator
82
+ );
83
+
84
+ checkSpacing(operatorToken);
50
85
  },
51
86
  VariableDeclarator(node) {
52
87
  if (node.init) {
53
- const operatorToken = sourceCode.getTokenBefore(node.init);
54
- checkSpacing(node, operatorToken, "=");
88
+ const operatorToken = sourceCode.getFirstTokenBetween(
89
+ node.id,
90
+ node.init,
91
+ token => token.value === "="
92
+ );
93
+
94
+ checkSpacing(operatorToken);
55
95
  }
56
96
  },
57
97
 
58
98
  Property(node) {
59
- if (checkColon) {
60
- const operatorToken = sourceCode.getTokenBefore(node.value);
61
- checkSpacing(node, operatorToken, ":");
99
+ if (checkColon && !node.method && !node.shorthand) {
100
+ const operatorToken = sourceCode.getFirstTokenBetween(
101
+ node.key,
102
+ node.value,
103
+ token => token.value === ":"
104
+ );
105
+
106
+ checkSpacing(operatorToken);
62
107
  }
63
108
  },
64
109
  };
@@ -15,13 +15,14 @@ export default {
15
15
  create(context) {
16
16
  return {
17
17
  Program(node) {
18
- const fileName = context.getFilename();
19
- const fileContent = fs.readFileSync(path.resolve(fileName), "utf8");
18
+ const fileName = context.physicalFilename || context.filename || context.getFilename();
20
19
 
21
20
  // Determine the file extension
22
21
  const fileExtension = path.extname(fileName);
23
22
 
24
23
  if (fileExtension === ".vue") {
24
+ const fileContent = fs.readFileSync(path.resolve(fileName), "utf8");
25
+
25
26
  // Define the headers
26
27
  const headers = ["TEMPLATE", "SCRIPT", "STYLE"];
27
28
 
@@ -31,7 +32,7 @@ export default {
31
32
  // Check each tag individually
32
33
  vueTags.forEach((tag, index) => {
33
34
  const tagRegExp = new RegExp(`<${tag}`);
34
- const headerRegExp = new RegExp(`\\n\\n<!--\\s*\\*{70,73}\\s*\\n\\s{5}${headers[index]}\\s*\\n\\s*\\*{70,73}\\s*-->\\s*\\n\\n<${tag}`);
35
+ const headerRegExp = new RegExp(`(?:^|\\n\\n)<!--\\s*\\*{70,73}\\s*\\n\\s{5}${headers[index]}\\s*\\n\\s*\\*{70,73}\\s*-->\\s*\\n\\n<${tag}`);
35
36
 
36
37
  // If the tag exists without the corresponding header, report an error
37
38
  if (tagRegExp.test(fileContent) && !headerRegExp.test(fileContent)) {
@@ -5,8 +5,7 @@ export default {
5
5
  description: "disallow regex in Vue data object",
6
6
  category: "Possible Errors",
7
7
  recommended: true,
8
- },
9
- fixable: "code",
8
+ }
10
9
  },
11
10
 
12
11
  create(context) {