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
@@ -0,0 +1,158 @@
1
+ const MESSAGE_ID = "singlePayloadArgument";
2
+
3
+ export default {
4
+ meta: {
5
+ type: "problem",
6
+ docs: {
7
+ description: "Require Pinia actions to accept at most one payload argument",
8
+ category: "Possible Errors",
9
+ recommended: true
10
+ },
11
+ schema: [],
12
+ messages: {
13
+ [MESSAGE_ID]: "Pinia actions must accept at most one payload argument. Wrap multiple values in an object."
14
+ }
15
+ },
16
+
17
+ create(context) {
18
+ const sourceCode = context.sourceCode || context.getSourceCode();
19
+ const defineStoreImports = collectDefineStoreImports(sourceCode.ast);
20
+
21
+ return {
22
+ CallExpression(node) {
23
+ if (!isImportedDefineStoreCall(node, sourceCode, defineStoreImports)) {
24
+ return;
25
+ }
26
+
27
+ const options = node.arguments[1];
28
+
29
+ if (!options || options.type !== "ObjectExpression") {
30
+ return;
31
+ }
32
+
33
+ const actions = findObjectProperty(options, "actions");
34
+
35
+ if (!actions || actions.value.type !== "ObjectExpression") {
36
+ return;
37
+ }
38
+
39
+ for (const property of actions.value.properties) {
40
+ const action = getActionFunction(property);
41
+
42
+ if (
43
+ action &&
44
+ (
45
+ action.params.length > 1 ||
46
+ action.params.some((parameter) => parameter.type === "RestElement")
47
+ )
48
+ ) {
49
+ context.report({
50
+ node: action,
51
+ messageId: MESSAGE_ID
52
+ });
53
+ }
54
+ }
55
+ }
56
+ };
57
+ }
58
+ };
59
+
60
+ // Collects local bindings for named defineStore imports from Pinia
61
+ function collectDefineStoreImports(program) {
62
+ const imports = new Map();
63
+
64
+ for (const node of program.body) {
65
+ if (node.type !== "ImportDeclaration" || node.source.value !== "pinia") {
66
+ continue;
67
+ }
68
+
69
+ for (const specifier of node.specifiers) {
70
+ if (
71
+ specifier.type === "ImportSpecifier" &&
72
+ getImportedName(specifier) === "defineStore"
73
+ ) {
74
+ imports.set(specifier.local.name, specifier);
75
+ }
76
+ }
77
+ }
78
+
79
+ return imports;
80
+ }
81
+
82
+ // Returns the imported name for JavaScript and TypeScript ESTree nodes
83
+ function getImportedName(specifier) {
84
+ return specifier.imported.name || specifier.imported.value;
85
+ }
86
+
87
+ // Checks that a call resolves to the collected Pinia import, rather than a shadow
88
+ function isImportedDefineStoreCall(node, sourceCode, defineStoreImports) {
89
+ if (
90
+ node.callee.type !== "Identifier" ||
91
+ !defineStoreImports.has(node.callee.name)
92
+ ) {
93
+ return false;
94
+ }
95
+
96
+ const expectedSpecifier = defineStoreImports.get(node.callee.name);
97
+ let scope = sourceCode.getScope(node);
98
+
99
+ while (scope) {
100
+ const variable = scope.variables.find(({ name }) => name === node.callee.name);
101
+
102
+ if (variable) {
103
+ return variable.defs.some((definition) => {
104
+ return (
105
+ definition.type === "ImportBinding" &&
106
+ definition.node === expectedSpecifier
107
+ );
108
+ });
109
+ }
110
+
111
+ scope = scope.upper;
112
+ }
113
+
114
+ return false;
115
+ }
116
+
117
+ // Finds a direct, statically named property in an object expression
118
+ function findObjectProperty(object, name) {
119
+ return object.properties.find((property) => {
120
+ return (
121
+ property.type === "Property" &&
122
+ property.kind === "init" &&
123
+ getStaticPropertyName(property) === name
124
+ );
125
+ });
126
+ }
127
+
128
+ // Returns a property's static name, including quoted property names
129
+ function getStaticPropertyName(property) {
130
+ if (!property.computed && property.key.type === "Identifier") {
131
+ return property.key.name;
132
+ }
133
+
134
+ if (property.key.type === "Literal") {
135
+ return property.key.value;
136
+ }
137
+
138
+ return null;
139
+ }
140
+
141
+ // Returns a directly declared action function
142
+ function getActionFunction(property) {
143
+ if (
144
+ property.type !== "Property" ||
145
+ property.kind !== "init"
146
+ ) {
147
+ return null;
148
+ }
149
+
150
+ if (
151
+ property.value.type === "FunctionExpression" ||
152
+ property.value.type === "ArrowFunctionExpression"
153
+ ) {
154
+ return property.value;
155
+ }
156
+
157
+ return null;
158
+ }
@@ -28,9 +28,14 @@ export default {
28
28
  fix(fixer) {
29
29
  const typeToken = sourceCode.getLastToken(typeProperty);
30
30
  const defaultToken = sourceCode.getFirstToken(defaultProperty);
31
- const textBetween = sourceCode.text.slice(typeToken.range[1], defaultToken.range[0]);
32
- const newTextBetween = textBetween.replace(/,?\s*/, ",\n\n");
33
- return fixer.replaceTextRange([typeToken.range[1], defaultToken.range[0]], newTextBetween);
31
+ const typeLine = sourceCode.lines[typeProperty.loc.start.line - 1];
32
+ const leadingText = typeLine.slice(0, typeProperty.loc.start.column);
33
+ const indent = leadingText.trim() === "" ? leadingText : "";
34
+
35
+ return fixer.replaceTextRange(
36
+ [typeToken.range[1], defaultToken.range[0]],
37
+ `,\n\n${indent}`
38
+ );
34
39
  }
35
40
  });
36
41
  }
@@ -30,6 +30,29 @@ function hasValidSignature(handler) {
30
30
  });
31
31
  }
32
32
 
33
+ function isDefineComponentCall(node) {
34
+ if (!node || node.type !== "CallExpression") {
35
+ return false;
36
+ }
37
+
38
+ return (
39
+ (node.callee.type === "Identifier" && node.callee.name === "defineComponent") ||
40
+ (
41
+ node.callee.type === "MemberExpression" &&
42
+ !node.callee.computed &&
43
+ node.callee.property.type === "Identifier" &&
44
+ node.callee.property.name === "defineComponent"
45
+ )
46
+ );
47
+ }
48
+
49
+ function isVueOptionsObject(node) {
50
+ return (
51
+ node.parent.type === "ExportDefaultDeclaration" ||
52
+ isDefineComponentCall(node.parent)
53
+ );
54
+ }
55
+
33
56
  export default {
34
57
  meta: {
35
58
  type: "suggestion",
@@ -52,7 +75,17 @@ export default {
52
75
 
53
76
  create(context) {
54
77
  return {
55
- 'Property[key.name="watch"] > ObjectExpression'(node) {
78
+ "Property > ObjectExpression"(node) {
79
+ const watchProperty = node.parent;
80
+ const optionsObject = watchProperty.parent;
81
+
82
+ if (
83
+ getPropertyName(watchProperty) !== "watch" ||
84
+ !isVueOptionsObject(optionsObject)
85
+ ) {
86
+ return;
87
+ }
88
+
56
89
  node.properties.forEach((watcher) => {
57
90
  if (watcher.type !== "Property") {
58
91
  return;
@@ -0,0 +1,19 @@
1
+
2
+
3
+ <!-- **********************************************************************
4
+ TEMPLATE
5
+ ********************************************************************** -->
6
+
7
+ <template>
8
+ <main>Partial headers</main>
9
+ </template>
10
+
11
+ <script setup>
12
+ const ready = false;
13
+ </script>
14
+
15
+ <style scoped>
16
+ main {
17
+ display: block;
18
+ }
19
+ </style>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <main>Missing header</main>
3
+ </template>
@@ -0,0 +1,27 @@
1
+ <!-- **********************************************************************
2
+ TEMPLATE
3
+ ********************************************************************** -->
4
+
5
+ <template>
6
+ <main>Ready</main>
7
+ </template>
8
+
9
+
10
+ <!-- **********************************************************************
11
+ SCRIPT
12
+ ********************************************************************** -->
13
+
14
+ <script setup>
15
+ const ready = true;
16
+ </script>
17
+
18
+
19
+ <!-- **********************************************************************
20
+ STYLE
21
+ ********************************************************************** -->
22
+
23
+ <style scoped>
24
+ main {
25
+ display: block;
26
+ }
27
+ </style>
@@ -0,0 +1,78 @@
1
+ import { RuleTester } from "eslint";
2
+ import { describe, it } from "node:test";
3
+ import vueParser from "vue-eslint-parser";
4
+
5
+ RuleTester.describe = describe;
6
+ RuleTester.it = it;
7
+ RuleTester.itOnly = it.only;
8
+
9
+ export default function createRuleTester({
10
+ languageOptions = {},
11
+
12
+ ...options
13
+ } = {}) {
14
+ return new RuleTester({
15
+ ...options,
16
+
17
+ languageOptions: {
18
+ ecmaVersion: "latest",
19
+ sourceType: "module",
20
+
21
+ ...languageOptions
22
+ }
23
+ });
24
+ }
25
+
26
+ export function createVueRuleTester(options = {}) {
27
+ return createRuleTester({
28
+ ...options,
29
+
30
+ languageOptions: {
31
+ ...options.languageOptions,
32
+
33
+ parser: vueParser
34
+ }
35
+ });
36
+ }
37
+
38
+ export function createPugRuleTester(options = {}) {
39
+ return createVueRuleTester({
40
+ ...options,
41
+
42
+ languageOptions: {
43
+ ...options.languageOptions,
44
+
45
+ parserOptions: {
46
+ ...options.languageOptions?.parserOptions,
47
+
48
+ templateTokenizer: {
49
+ pug: "vue-eslint-parser-template-tokenizer-pug"
50
+ }
51
+ }
52
+ }
53
+ });
54
+ }
55
+
56
+ export function withoutFixes(rule) {
57
+ const { fixable, ...meta } = rule.meta || {};
58
+
59
+ return {
60
+ ...rule,
61
+
62
+ meta,
63
+
64
+ create(context) {
65
+ const reportOnlyContext = Object.create(context);
66
+
67
+ Object.defineProperty(reportOnlyContext, "report", {
68
+ value(descriptor) {
69
+ const { fix, ...report } = descriptor;
70
+
71
+ context.report(report);
72
+ }
73
+ });
74
+
75
+ return rule.create(reportOnlyContext);
76
+ }
77
+ };
78
+ }
@@ -0,0 +1,25 @@
1
+ import rule from "../../rules/align-comments.js";
2
+ import createRuleTester from "../rule-tester.js";
3
+
4
+ const ruleTester = createRuleTester();
5
+
6
+ ruleTester.run("align-comments", rule, {
7
+ valid: [
8
+ "// A top-level value\nconst value = 1;",
9
+ "function example() {\n // Return the value\n return true;\n}",
10
+ "const previous = 1; // Keep this inline\nconst next = 2;",
11
+ "const callback = () => {\n // Invoke the callback\n callback();\n};"
12
+ ],
13
+
14
+ invalid: [
15
+ {
16
+ code: "function example() {\n// Misaligned\n return true;\n}",
17
+ output: "function example() {\n // Misaligned\n return true;\n}",
18
+ errors: [
19
+ {
20
+ message: "Comment should be aligned with the next line."
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ });
@@ -0,0 +1,24 @@
1
+ import rule from "../../rules/align-consecutive-class-assignements.js";
2
+ import createRuleTester from "../rule-tester.js";
3
+
4
+ const ruleTester = createRuleTester();
5
+
6
+ ruleTester.run("align-consecutive-class-assignements", rule, {
7
+ valid: [
8
+ "class Example {\n constructor() {\n this.a = 1;\n this.long = 2;\n }\n}",
9
+ "class Example {\n constructor() {\n this.a = 1;\n\n this.long = 2;\n }\n}",
10
+ "class Example {\n method() {\n this.a = 1;\n this.long = 2;\n }\n}"
11
+ ],
12
+
13
+ invalid: [
14
+ {
15
+ code: "class Example {\n constructor() {\n this.a = 1;\n this.long = 2;\n }\n}",
16
+ errors: [
17
+ {
18
+ message:
19
+ "Assignment of 'this.a' at line 3 column 12 should be aligned at column 15"
20
+ }
21
+ ]
22
+ }
23
+ ]
24
+ });
@@ -0,0 +1,31 @@
1
+ import rule from "../../rules/align-one-var.js";
2
+ import createRuleTester from "../rule-tester.js";
3
+
4
+ const ruleTester = createRuleTester();
5
+
6
+ ruleTester.run("align-one-var", rule, {
7
+ valid: [
8
+ "const first = 1,\n next = 2;",
9
+ "const first = 1, next = 2;",
10
+ "let first,\n next;"
11
+ ],
12
+
13
+ invalid: [
14
+ {
15
+ code: "const first = 1,\n next = 2;",
16
+ errors: [
17
+ {
18
+ message: "Misaligned variable declaration."
19
+ }
20
+ ]
21
+ },
22
+ {
23
+ code: "const first = 1,\n next = 2;",
24
+ errors: [
25
+ {
26
+ message: "Misaligned assignment operator."
27
+ }
28
+ ]
29
+ }
30
+ ]
31
+ });
@@ -0,0 +1,27 @@
1
+ import rule from "../../rules/align-requires.js";
2
+ import createRuleTester from "../rule-tester.js";
3
+
4
+ const ruleTester = createRuleTester({
5
+ languageOptions: {
6
+ sourceType: "commonjs"
7
+ }
8
+ });
9
+
10
+ ruleTester.run("align-requires", rule, {
11
+ valid: [
12
+ "const alpha = require(\"alpha\"),\n beta = require(\"beta\");",
13
+ "const alpha = require(\"alpha\");\nconst beta = require(\"beta\");",
14
+ "const alpha = require(\"alpha\"), value = 1,\n beta = require(\"beta\");"
15
+ ],
16
+
17
+ invalid: [
18
+ {
19
+ code: "const alpha = require(\"alpha\"),\n beta = require(\"beta\");",
20
+ errors: [
21
+ {
22
+ message: "Misaligned require statement."
23
+ }
24
+ ]
25
+ }
26
+ ]
27
+ });
@@ -0,0 +1,23 @@
1
+ import rule from "../../rules/arrow-block-newline.js";
2
+ import createRuleTester from "../rule-tester.js";
3
+
4
+ const ruleTester = createRuleTester();
5
+
6
+ ruleTester.run("arrow-block-newline", rule, {
7
+ valid: [
8
+ "const callback = () => {};",
9
+ "const callback = () => {\n return true;\n};",
10
+ "const callback = () => true;"
11
+ ],
12
+
13
+ invalid: [
14
+ {
15
+ code: "const callback = () => { return true;\n};",
16
+ errors: [
17
+ {
18
+ messageId: "expectedNewline"
19
+ }
20
+ ]
21
+ }
22
+ ]
23
+ });
@@ -0,0 +1,27 @@
1
+ import rule from "../../rules/consecutive-line-comments.js";
2
+ import createRuleTester from "../rule-tester.js";
3
+
4
+ const ruleTester = createRuleTester();
5
+
6
+ const lines = (...values) => values.join("\n");
7
+
8
+ ruleTester.run("consecutive-line-comments", rule, {
9
+ valid: [
10
+ lines("// First line \\", "// second line", "const value = 1;"),
11
+ lines("// First comment", "// Second comment", "const value = 1;"),
12
+ lines("// eslint-disable-next-line no-console", "console.log(\"value\");"),
13
+ lines("// Intro \\", "// Important: separate block", "const value = 1;")
14
+ ],
15
+
16
+ invalid: [
17
+ {
18
+ code: lines("// First line \\", "// Second line", "const value = 1;"),
19
+ output: lines("// First line \\", "// Second line", "const value = 1;"),
20
+ errors: [
21
+ {
22
+ messageId: "format"
23
+ }
24
+ ]
25
+ }
26
+ ]
27
+ });
@@ -0,0 +1,52 @@
1
+ import rule from "../../rules/const.js";
2
+ import createRuleTester from "../rule-tester.js";
3
+
4
+ const ruleTester = createRuleTester();
5
+
6
+ ruleTester.run("const", rule, {
7
+ valid: [
8
+ "const MAXIMUM = 10;",
9
+ "const client = createClient();",
10
+ "const callback = () => true;",
11
+ "function example() {\n const __CACHE = 1;\n}",
12
+ {
13
+ code: "function example() {\n const cache = 1;\n}",
14
+ options: [
15
+ {
16
+ exceptions: ["cache"]
17
+ }
18
+ ]
19
+ },
20
+ "export const publicValue = 1;"
21
+ ],
22
+
23
+ invalid: [
24
+ {
25
+ code: "const value = 1;",
26
+ errors: [
27
+ {
28
+ message:
29
+ "Consts should be uppercase and defined at the top level or be replaced with let _variable (Crisp convention)."
30
+ }
31
+ ]
32
+ },
33
+ {
34
+ code: "function example() {\n const _cache = 1;\n}",
35
+ output: "function example() {\n let _cache = 1;\n}",
36
+ errors: [
37
+ {
38
+ message: "Local variables prefixed with \"_\" should use \"let\"."
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ code: "function example() {\n const value = 1;\n}",
44
+ errors: [
45
+ {
46
+ message:
47
+ "Consts should be uppercase and defined at the top level or be replaced with let _variable (Crisp convention)."
48
+ }
49
+ ]
50
+ }
51
+ ]
52
+ });
@@ -0,0 +1,52 @@
1
+ import rule from "../../rules/constructor-variables.js";
2
+ import createRuleTester from "../rule-tester.js";
3
+
4
+ const ruleTester = createRuleTester();
5
+
6
+ ruleTester.run("constructor-variables", rule, {
7
+ valid: [
8
+ "class Example {\n constructor() {\n this._value = 1;\n }\n}",
9
+ {
10
+ code: "class Example {\n constructor() {\n this.value = 1;\n }\n}",
11
+ options: [
12
+ {
13
+ variableExceptions: ["value"]
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ code: "class Example {\n constructor() {\n this.value = 1;\n }\n}",
19
+ filename: "/project/special-file.js",
20
+ options: [
21
+ {
22
+ filenameExceptions: ["special-file.js"]
23
+ }
24
+ ]
25
+ },
26
+ "class Example {\n method() {\n this.value = 1;\n }\n}",
27
+ "class Example {\n constructor() {\n this[\"_value\"] = 1;\n }\n}",
28
+ "class Example {\n constructor() {\n this[propertyName] = 1;\n }\n}"
29
+ ],
30
+
31
+ invalid: [
32
+ {
33
+ code: "class Example {\n constructor() {\n this.value = 1;\n }\n}",
34
+ filename: "/project/example.js",
35
+ errors: [
36
+ {
37
+ message:
38
+ "Class properties in the constructor should start with '_', except for specified exceptions"
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ code: "class Example {\n constructor() {\n this[\"value\"] = 1;\n }\n}",
44
+ errors: [
45
+ {
46
+ message:
47
+ "Class properties in the constructor should start with '_', except for specified exceptions"
48
+ }
49
+ ]
50
+ }
51
+ ]
52
+ });
@@ -0,0 +1,24 @@
1
+ import rule from "../../rules/enforce-optional.js";
2
+ import createRuleTester from "../rule-tester.js";
3
+
4
+ const ruleTester = createRuleTester();
5
+
6
+ ruleTester.run("enforce-optional", rule, {
7
+ valid: [
8
+ "const result = foo?.bar;",
9
+ "const result = foo && foo.bar !== null;",
10
+ "const keys = Object.keys(foo || {});",
11
+ "const filtered = (foo || []).filter(Boolean);"
12
+ ],
13
+
14
+ invalid: [
15
+ {
16
+ code: "const result = foo && foo.bar;",
17
+ errors: [
18
+ {
19
+ message: /Prefer using optional chaining rather than/
20
+ }
21
+ ]
22
+ }
23
+ ]
24
+ });