@dineroregnskab/eslint-plugin-custom-rules 2.1.3 → 3.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/.prettierrc +12 -0
- package/.vscode/settings.json +3 -0
- package/README.md +11 -17
- package/eslint-plugin-custom-rules.js +16 -15
- package/package.json +12 -5
- package/rules/camel-case-attributes.js +16 -11
- package/rules/dayjs-with-timezone.js +6 -6
- package/rules/disallow-multiple-calls.js +33 -0
- package/rules/disallow-signal-property-reassignment.js +35 -0
- package/rules/filter-before-take.js +58 -0
- package/rules/no-viewencapsulation-none.js +51 -0
- package/rules/reducers-should-always-return.js +3 -1
- package/rules/replace-first-with-take.js +15 -69
- package/rules/signal-naming-convention.js +61 -0
- package/rules/use-danish-currency-pipe.js +7 -7
package/.prettierrc
ADDED
package/README.md
CHANGED
|
@@ -18,39 +18,34 @@ Run `npm i` in root and in `/example`.
|
|
|
18
18
|
|
|
19
19
|
### Adding rules
|
|
20
20
|
|
|
21
|
-
-
|
|
21
|
+
- Create a new js rule in this directory: `./rules`
|
|
22
22
|
|
|
23
|
-
- Add the new
|
|
23
|
+
- Add the new rule to this file `./eslint-plugin-custom-rules.js`.
|
|
24
24
|
|
|
25
|
-
-
|
|
25
|
+
- Add the new rule to `./example/.eslintrc.json`
|
|
26
26
|
|
|
27
|
-
-
|
|
27
|
+
- Test the rule by adding some HTML/TS code here `./example/test.html` or `./example/test.ts` and restart the ESLint server in vs code by pressing F1 -> ESLint: Restart ESLint Server.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
- You can also test the rules via the terminal directly without reloading eslint server and to see code debugging in the rule definition:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
npm run testhtml
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Add ts rules in `test.ts` and run test with
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
33
|
npm run testts
|
|
39
34
|
```
|
|
40
35
|
|
|
41
36
|
#### Debugging what the eslint sees
|
|
42
37
|
|
|
43
|
-
Add a rule, and log it with console log, to see in terminal
|
|
38
|
+
Add a rule, and log it with console log, to see in terminal if the element function even runs "Program" in this case. Then run `npm run testhtml` npm script to see it in terminal.
|
|
44
39
|
|
|
45
40
|
```js
|
|
46
41
|
module.exports = {
|
|
47
42
|
meta: {
|
|
48
|
-
type:
|
|
43
|
+
type: 'suggestion',
|
|
49
44
|
docs: {
|
|
50
45
|
description:
|
|
51
|
-
|
|
46
|
+
'Enforce using `danishCurrency` pipe instead of `currency` pipe in Angular HTML templates.',
|
|
52
47
|
},
|
|
53
|
-
fixable:
|
|
48
|
+
fixable: 'code',
|
|
54
49
|
schema: [],
|
|
55
50
|
},
|
|
56
51
|
|
|
@@ -58,12 +53,11 @@ module.exports = {
|
|
|
58
53
|
return {
|
|
59
54
|
// Target the entire file and traverse each node
|
|
60
55
|
Program(node) {
|
|
61
|
-
console.log(
|
|
56
|
+
console.log('Parsed Node Types:', node);
|
|
62
57
|
},
|
|
63
58
|
};
|
|
64
59
|
},
|
|
65
60
|
};
|
|
66
|
-
|
|
67
61
|
```
|
|
68
62
|
|
|
69
63
|
### Publish & install new rule locally
|
|
@@ -89,7 +83,7 @@ Example:
|
|
|
89
83
|
|
|
90
84
|
```bash
|
|
91
85
|
npm i
|
|
92
|
-
|
|
86
|
+
```
|
|
93
87
|
|
|
94
88
|
> **Note: You need to restart ESLint to apply new rules. Restart the ESLint server in vs code by pressing F1 -> ESLint: Restart ESLint Server or F1 -> reload window**
|
|
95
89
|
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"use-danish-currency-pipe": useDanishCurrencyPipeRule,
|
|
13
|
-
"replace-first-with-take": replaceFirstWithTakeRule,
|
|
14
|
-
},
|
|
1
|
+
const rules = {
|
|
2
|
+
'reducers-should-always-return': require('./rules/reducers-should-always-return'),
|
|
3
|
+
'attr-camel-case-rule': require('./rules/camel-case-attributes'),
|
|
4
|
+
'dayjs-with-timezone': require('./rules/dayjs-with-timezone'),
|
|
5
|
+
'use-danish-currency-pipe': require('./rules/use-danish-currency-pipe'),
|
|
6
|
+
'replace-first-with-take': require('./rules/replace-first-with-take'),
|
|
7
|
+
'disallow-multiple-calls': require('./rules/disallow-multiple-calls'),
|
|
8
|
+
'disallow-signal-property-reassignment': require('./rules/disallow-signal-property-reassignment'),
|
|
9
|
+
'signal-naming-convention': require('./rules/signal-naming-convention'),
|
|
10
|
+
'filter-before-take': require('./rules/filter-before-take'),
|
|
11
|
+
'no-viewencapsulation-none': require('./rules/no-viewencapsulation-none'),
|
|
15
12
|
};
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
console.log('Custom ESLint rules loaded:', Object.keys(rules)); // Debug log
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
rules,
|
|
18
|
+
};
|
package/package.json
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dineroregnskab/eslint-plugin-custom-rules",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "ESLint plugin with custom rules for Dinero Regnskab",
|
|
5
5
|
"main": "eslint-plugin-custom-rules.js",
|
|
6
|
-
"scripts": {
|
|
6
|
+
"scripts": {
|
|
7
|
+
"prettier:write": "prettier \"./**/*.{ts,html,md,scss,js}\" --write"
|
|
8
|
+
},
|
|
7
9
|
"keywords": [],
|
|
8
10
|
"author": "",
|
|
9
11
|
"license": "ISC",
|
|
10
12
|
"devDependencies": {
|
|
11
|
-
"eslint": "^
|
|
13
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
14
|
+
"eslint": "8.57.1",
|
|
15
|
+
"eslint-config-prettier": "^9.1.0",
|
|
16
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
17
|
+
"prettier": "^3.4.1"
|
|
12
18
|
},
|
|
13
19
|
"peerDependencies": {
|
|
14
|
-
"eslint": ">=8.0.0"
|
|
20
|
+
"eslint": ">=8.0.0",
|
|
21
|
+
"@typescript-eslint/parser": ">=7"
|
|
15
22
|
},
|
|
16
23
|
"files": [
|
|
17
24
|
"**/*",
|
|
18
25
|
"!example/**/*"
|
|
19
26
|
],
|
|
20
27
|
"dependencies": {
|
|
21
|
-
"@angular-eslint/template-parser": "
|
|
28
|
+
"@angular-eslint/template-parser": "17.5.3"
|
|
22
29
|
}
|
|
23
30
|
}
|
|
@@ -11,18 +11,23 @@ module.exports = {
|
|
|
11
11
|
create(context) {
|
|
12
12
|
return {
|
|
13
13
|
TextAttribute(node) {
|
|
14
|
-
if (
|
|
15
|
-
|
|
14
|
+
if (
|
|
15
|
+
(node.name === 'data-cy' || node.name === 'id') &&
|
|
16
|
+
node.value
|
|
17
|
+
) {
|
|
18
|
+
const camelCasedValue = new RegExp(
|
|
19
|
+
/^[a-z]+([A-Z]?[a-z]*)*$/,
|
|
20
|
+
);
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
if (!camelCasedValue.test(node.value)) {
|
|
23
|
+
context.report({
|
|
24
|
+
node,
|
|
25
|
+
message:
|
|
26
|
+
'The value of data-cy and id attributes should be in camelCase.',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
24
29
|
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
27
32
|
},
|
|
28
33
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
meta: {
|
|
3
|
-
type:
|
|
3
|
+
type: 'suggestion',
|
|
4
4
|
docs: {
|
|
5
|
-
description:
|
|
6
|
-
category:
|
|
5
|
+
description: 'Enforce using dayjs.tz() instead of dayjs()',
|
|
6
|
+
category: 'Best Practices',
|
|
7
7
|
recommended: false,
|
|
8
8
|
},
|
|
9
9
|
fixable: null,
|
|
@@ -14,13 +14,13 @@ module.exports = {
|
|
|
14
14
|
return {
|
|
15
15
|
CallExpression(node) {
|
|
16
16
|
if (
|
|
17
|
-
node.callee.type ===
|
|
18
|
-
node.callee.name ===
|
|
17
|
+
node.callee.type === 'Identifier' &&
|
|
18
|
+
node.callee.name === 'dayjs'
|
|
19
19
|
) {
|
|
20
20
|
context.report({
|
|
21
21
|
node,
|
|
22
22
|
message:
|
|
23
|
-
|
|
23
|
+
'Consider using dayjs.tz() instead of dayjs() for timezone support',
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
},
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'suggestion',
|
|
4
|
+
docs: {
|
|
5
|
+
description:
|
|
6
|
+
'Disallow multiple function calls in a single event binding.',
|
|
7
|
+
},
|
|
8
|
+
schema: [], // No options for this rule
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
create(context) {
|
|
12
|
+
return {
|
|
13
|
+
BoundEvent(node) {
|
|
14
|
+
if (
|
|
15
|
+
node.handler &&
|
|
16
|
+
node.handler.ast &&
|
|
17
|
+
node.handler.ast.type === 'Chain'
|
|
18
|
+
) {
|
|
19
|
+
const expressions = node.handler.ast.expressions;
|
|
20
|
+
|
|
21
|
+
// If there are multiple expressions (function calls), report
|
|
22
|
+
if (expressions.length > 1) {
|
|
23
|
+
context.report({
|
|
24
|
+
node,
|
|
25
|
+
message:
|
|
26
|
+
'Multiple function calls are not allowed in a single event binding. Use one function per event.',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description:
|
|
6
|
+
'Avoid reassigning properties of signal values, as it will fail at runtime.',
|
|
7
|
+
},
|
|
8
|
+
schema: [], // No options for this rule
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
create(context) {
|
|
12
|
+
return {
|
|
13
|
+
AssignmentExpression(node) {
|
|
14
|
+
// Check if the left-hand side is a MemberExpression
|
|
15
|
+
if (
|
|
16
|
+
node.left.type === 'MemberExpression' &&
|
|
17
|
+
node.left.object.type === 'CallExpression' &&
|
|
18
|
+
node.left.object.callee.type === 'MemberExpression' &&
|
|
19
|
+
node.left.object.callee.property.name === 'get' && // Match `.get()` method
|
|
20
|
+
node.left.object.callee.object.type ===
|
|
21
|
+
'MemberExpression' &&
|
|
22
|
+
node.left.object.callee.object.property.name.endsWith(
|
|
23
|
+
'Signal',
|
|
24
|
+
) // Match signals
|
|
25
|
+
) {
|
|
26
|
+
context.report({
|
|
27
|
+
node,
|
|
28
|
+
message:
|
|
29
|
+
'Reassigning properties of a signal value will fail at runtime. Use a setter or update mechanism instead.',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'suggestion',
|
|
4
|
+
docs: {
|
|
5
|
+
description:
|
|
6
|
+
'Ensure `filter()` comes before `take(1)` in a `pipe()`.',
|
|
7
|
+
},
|
|
8
|
+
messages: {
|
|
9
|
+
filterBeforeTake:
|
|
10
|
+
'`filter()` must come before `take(1)` in a `pipe()` to ensure proper data filtering.',
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
create(context) {
|
|
16
|
+
return {
|
|
17
|
+
CallExpression(node) {
|
|
18
|
+
// Check if the callee is a `pipe()` method
|
|
19
|
+
if (
|
|
20
|
+
node.callee.type === 'MemberExpression' &&
|
|
21
|
+
node.callee.property.name === 'pipe'
|
|
22
|
+
) {
|
|
23
|
+
const pipeArguments = node.arguments;
|
|
24
|
+
|
|
25
|
+
let filterIndex = -1;
|
|
26
|
+
let takeIndex = -1;
|
|
27
|
+
|
|
28
|
+
// Iterate over arguments to find the positions of `filter` and `take(1)`
|
|
29
|
+
pipeArguments.forEach((arg, index) => {
|
|
30
|
+
if (
|
|
31
|
+
arg.type === 'CallExpression' &&
|
|
32
|
+
arg.callee.name === 'filter'
|
|
33
|
+
) {
|
|
34
|
+
filterIndex = index;
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
arg.type === 'CallExpression' &&
|
|
38
|
+
arg.callee.name === 'take' &&
|
|
39
|
+
arg.arguments.length === 1 &&
|
|
40
|
+
arg.arguments[0].type === 'Literal' &&
|
|
41
|
+
arg.arguments[0].value === 1
|
|
42
|
+
) {
|
|
43
|
+
takeIndex = index;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Report if `take(1)` appears before `filter`
|
|
48
|
+
if (takeIndex !== -1 && filterIndex > takeIndex) {
|
|
49
|
+
context.report({
|
|
50
|
+
node: pipeArguments[takeIndex],
|
|
51
|
+
messageId: 'filterBeforeTake',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description:
|
|
6
|
+
'Warn against using `encapsulation: ViewEncapsulation.None` in `@Component` because it breaks Angular modularity.',
|
|
7
|
+
},
|
|
8
|
+
messages: {
|
|
9
|
+
noViewEncapsulationNone:
|
|
10
|
+
'Avoid using `ViewEncapsulation.None` as it disables Angulars modular styling system, which can lead to unintended style leakage and maintenance challenges. Use it only when absolutely necessary and no alternative solution exists.',
|
|
11
|
+
},
|
|
12
|
+
schema: [], // No options
|
|
13
|
+
},
|
|
14
|
+
create(context) {
|
|
15
|
+
return {
|
|
16
|
+
CallExpression(node) {
|
|
17
|
+
// Check if the decorator is `@Component`
|
|
18
|
+
if (
|
|
19
|
+
node.callee.type === 'Identifier' &&
|
|
20
|
+
node.callee.name === 'Component'
|
|
21
|
+
) {
|
|
22
|
+
const decoratorArguments = node.arguments;
|
|
23
|
+
if (
|
|
24
|
+
decoratorArguments.length === 1 &&
|
|
25
|
+
decoratorArguments[0].type === 'ObjectExpression'
|
|
26
|
+
) {
|
|
27
|
+
const properties = decoratorArguments[0].properties;
|
|
28
|
+
|
|
29
|
+
// Look for `encapsulation` property with value `ViewEncapsulation.None`
|
|
30
|
+
const encapsulationProperty = properties.find(
|
|
31
|
+
(prop) =>
|
|
32
|
+
prop.type === 'Property' &&
|
|
33
|
+
prop.key.name === 'encapsulation' &&
|
|
34
|
+
prop.value.type === 'MemberExpression' &&
|
|
35
|
+
prop.value.object.name ===
|
|
36
|
+
'ViewEncapsulation' &&
|
|
37
|
+
prop.value.property.name === 'None',
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (encapsulationProperty) {
|
|
41
|
+
context.report({
|
|
42
|
+
node: encapsulationProperty,
|
|
43
|
+
messageId: 'noViewEncapsulationNone',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -10,7 +10,9 @@ function hasReturnStatement(node) {
|
|
|
10
10
|
if (node.type === 'IfStatement') {
|
|
11
11
|
return (
|
|
12
12
|
hasReturnStatement(node.consequent) &&
|
|
13
|
-
(node.alternate !== null
|
|
13
|
+
(node.alternate !== null
|
|
14
|
+
? hasReturnStatement(node.alternate)
|
|
15
|
+
: false)
|
|
14
16
|
);
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
const eslintUtils = require("eslint-utils");
|
|
2
|
-
|
|
3
1
|
module.exports = {
|
|
4
2
|
meta: {
|
|
5
|
-
type:
|
|
3
|
+
type: 'suggestion',
|
|
6
4
|
docs: {
|
|
7
5
|
description:
|
|
8
|
-
|
|
6
|
+
'Replace `first()` with `take(1)` and require `filter()` to be used with `take(1)` in pipe.',
|
|
9
7
|
},
|
|
10
|
-
fixable:
|
|
8
|
+
fixable: 'code',
|
|
11
9
|
schema: [],
|
|
12
10
|
},
|
|
13
11
|
|
|
@@ -17,98 +15,46 @@ module.exports = {
|
|
|
17
15
|
CallExpression(node) {
|
|
18
16
|
// Check if the callee (function being called) is `first`
|
|
19
17
|
if (
|
|
20
|
-
node.callee.type ===
|
|
21
|
-
node.callee.name ===
|
|
18
|
+
node.callee.type === 'Identifier' &&
|
|
19
|
+
node.callee.name === 'first' &&
|
|
22
20
|
node.arguments.length === 0
|
|
23
21
|
) {
|
|
24
22
|
context.report({
|
|
25
23
|
node,
|
|
26
24
|
message:
|
|
27
|
-
|
|
28
|
-
fix(fixer) {
|
|
29
|
-
return fixer.replaceText(node, "take(1)");
|
|
30
|
-
},
|
|
25
|
+
'Replace `first()` with `take(1)` to avoid errors if no value is ever emitted.',
|
|
31
26
|
});
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
// Check for `pipe()` with `take(1)`
|
|
35
30
|
if (
|
|
36
|
-
node.callee.type ===
|
|
37
|
-
node.callee.property.name ===
|
|
31
|
+
node.callee.type === 'MemberExpression' &&
|
|
32
|
+
node.callee.property.name === 'pipe'
|
|
38
33
|
) {
|
|
39
34
|
const pipeArguments = node.arguments;
|
|
40
35
|
|
|
41
36
|
// Check if `take(1)` is present
|
|
42
37
|
const hasTakeOne = pipeArguments.some(
|
|
43
38
|
(arg) =>
|
|
44
|
-
arg.type ===
|
|
45
|
-
arg.callee.name ===
|
|
39
|
+
arg.type === 'CallExpression' &&
|
|
40
|
+
arg.callee.name === 'take' &&
|
|
46
41
|
arg.arguments.length === 1 &&
|
|
47
|
-
arg.arguments[0].value === 1
|
|
42
|
+
arg.arguments[0].value === 1,
|
|
48
43
|
);
|
|
49
44
|
|
|
50
45
|
// Check if `filter()` is present
|
|
51
46
|
const hasFilter = pipeArguments.some(
|
|
52
47
|
(arg) =>
|
|
53
|
-
arg.type ===
|
|
54
|
-
arg.callee.name ===
|
|
48
|
+
arg.type === 'CallExpression' &&
|
|
49
|
+
arg.callee.name === 'filter',
|
|
55
50
|
);
|
|
56
51
|
|
|
57
|
-
//
|
|
52
|
+
// Report an error if `take(1)` is used without `filter()`
|
|
58
53
|
if (hasTakeOne && !hasFilter) {
|
|
59
54
|
context.report({
|
|
60
55
|
node,
|
|
61
56
|
message:
|
|
62
|
-
|
|
63
|
-
fix(fixer) {
|
|
64
|
-
const sourceCode = context.getSourceCode();
|
|
65
|
-
const firstArg = pipeArguments[0];
|
|
66
|
-
|
|
67
|
-
const fixes = [
|
|
68
|
-
// Add the `filter()` inside the pipe
|
|
69
|
-
fixer.insertTextBefore(
|
|
70
|
-
firstArg,
|
|
71
|
-
"filter((value) => Boolean(value ?? false)), "
|
|
72
|
-
),
|
|
73
|
-
];
|
|
74
|
-
|
|
75
|
-
// Check if `filter` is imported, if not, add the import
|
|
76
|
-
const importDeclaration =
|
|
77
|
-
sourceCode.ast.body.find(
|
|
78
|
-
(node) =>
|
|
79
|
-
node.type === "ImportDeclaration" &&
|
|
80
|
-
node.source.value ===
|
|
81
|
-
"rxjs/operators"
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
if (importDeclaration) {
|
|
85
|
-
const filterImported =
|
|
86
|
-
importDeclaration.specifiers.some(
|
|
87
|
-
(specifier) =>
|
|
88
|
-
specifier.imported.name ===
|
|
89
|
-
"filter"
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
if (!filterImported) {
|
|
93
|
-
fixes.push(
|
|
94
|
-
fixer.insertTextBefore(
|
|
95
|
-
importDeclaration.specifiers[0],
|
|
96
|
-
"filter, "
|
|
97
|
-
)
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
} else {
|
|
101
|
-
// Add a new import if none exist
|
|
102
|
-
fixes.push(
|
|
103
|
-
fixer.insertTextBefore(
|
|
104
|
-
sourceCode.ast.body[0],
|
|
105
|
-
'import { filter } from "rxjs/operators";\n'
|
|
106
|
-
)
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return fixes;
|
|
111
|
-
},
|
|
57
|
+
'Using `take(1)` requires `filter()` to be used in the same pipe to avoid `null` or `undefined` values which will trigger an error. Example `filter((value) => Boolean(value ?? false)),`',
|
|
112
58
|
});
|
|
113
59
|
}
|
|
114
60
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'suggestion',
|
|
4
|
+
docs: {
|
|
5
|
+
description:
|
|
6
|
+
'Ensure variables initialized with "signal()" or "input()" have valid naming conventions.',
|
|
7
|
+
},
|
|
8
|
+
messages: {
|
|
9
|
+
missingSignal: 'Variable "{{ name }}" should end with "Signal".',
|
|
10
|
+
invalidSuffix:
|
|
11
|
+
'Variable "{{ name }}" should end with "Signal". Invalid suffix "{{ invalidSuffix }}".',
|
|
12
|
+
},
|
|
13
|
+
schema: [],
|
|
14
|
+
},
|
|
15
|
+
create(context) {
|
|
16
|
+
const invalidSuffixes = [
|
|
17
|
+
'ComputedSignal',
|
|
18
|
+
'WriteableSignal',
|
|
19
|
+
'InputSignal',
|
|
20
|
+
'OutputSignal',
|
|
21
|
+
'LinkedSignal',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
PropertyDefinition(node) {
|
|
26
|
+
// Check if the initializer exists and is a CallExpression
|
|
27
|
+
if (node.value && node.value.type === 'CallExpression') {
|
|
28
|
+
const { callee } = node.value;
|
|
29
|
+
|
|
30
|
+
// Check if the function called is named "signal" or "input"
|
|
31
|
+
if (
|
|
32
|
+
callee.type === 'Identifier' &&
|
|
33
|
+
(callee.name === 'signal' || callee.name === 'input')
|
|
34
|
+
) {
|
|
35
|
+
const variableName = node.key.name;
|
|
36
|
+
|
|
37
|
+
// Check for invalid suffixes
|
|
38
|
+
const invalidSuffix = invalidSuffixes.find((suffix) =>
|
|
39
|
+
variableName.endsWith(suffix),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (invalidSuffix) {
|
|
43
|
+
context.report({
|
|
44
|
+
node: node.key,
|
|
45
|
+
messageId: 'invalidSuffix',
|
|
46
|
+
data: { name: variableName, invalidSuffix },
|
|
47
|
+
});
|
|
48
|
+
} else if (!variableName.endsWith('Signal')) {
|
|
49
|
+
// Ensure the variable name ends with "Signal"
|
|
50
|
+
context.report({
|
|
51
|
+
node: node.key,
|
|
52
|
+
messageId: 'missingSignal',
|
|
53
|
+
data: { name: variableName },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
meta: {
|
|
3
|
-
type:
|
|
3
|
+
type: 'suggestion',
|
|
4
4
|
docs: {
|
|
5
5
|
description:
|
|
6
|
-
|
|
6
|
+
'Enforce using `danishCurrency` pipe instead of `currency` pipe in Angular HTML templates.',
|
|
7
7
|
},
|
|
8
|
-
fixable:
|
|
8
|
+
fixable: 'code',
|
|
9
9
|
schema: [],
|
|
10
10
|
},
|
|
11
11
|
|
|
@@ -24,18 +24,18 @@ module.exports = {
|
|
|
24
24
|
|
|
25
25
|
const startLine = templateContent
|
|
26
26
|
.slice(0, matchStart)
|
|
27
|
-
.split(
|
|
27
|
+
.split('\n').length;
|
|
28
28
|
const startColumn =
|
|
29
29
|
matchStart -
|
|
30
|
-
templateContent.lastIndexOf(
|
|
30
|
+
templateContent.lastIndexOf('\n', matchStart) -
|
|
31
31
|
1;
|
|
32
32
|
|
|
33
33
|
const endLine = templateContent
|
|
34
34
|
.slice(0, matchEnd)
|
|
35
|
-
.split(
|
|
35
|
+
.split('\n').length;
|
|
36
36
|
const endColumn =
|
|
37
37
|
matchEnd -
|
|
38
|
-
templateContent.lastIndexOf(
|
|
38
|
+
templateContent.lastIndexOf('\n', matchEnd) -
|
|
39
39
|
1;
|
|
40
40
|
|
|
41
41
|
context.report({
|