eslint-cdk-plugin 0.1.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/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/index.mjs +35 -0
- package/dist/no-class-in-interface.mjs +46 -0
- package/dist/no-construct-stack-suffix.mjs +192 -0
- package/dist/no-import-private.mjs +43 -0
- package/dist/no-mutable-props-interface.mjs +49 -0
- package/dist/no-mutable-public-fields.mjs +56 -0
- package/dist/no-parent-name-construct-id-match.mjs +257 -0
- package/dist/no-public-class-fields.mjs +111 -0
- package/dist/pascal-case-construct-id.mjs +69 -0
- package/dist/utils/convertString.mjs +18 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Ren Yamanashi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# eslint-plugin-cdk
|
|
2
|
+
|
|
3
|
+
ESLint plugin for [AWS CDK](https://github.com/aws/aws-cdk).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# npm
|
|
9
|
+
npm install -D @nigg/eslint-plugin-cdk
|
|
10
|
+
|
|
11
|
+
# yarn
|
|
12
|
+
yarn add -D @nigg/eslint-plugin-cdk
|
|
13
|
+
|
|
14
|
+
# pnpm
|
|
15
|
+
pnpm install -D @nigg/eslint-plugin-cdk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Use recommended config
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
// eslint.config.mjs
|
|
24
|
+
import eslintPluginCdk from "@nigg/eslint-plugin-cdk";
|
|
25
|
+
export default [
|
|
26
|
+
{
|
|
27
|
+
plugins: {
|
|
28
|
+
cdk: eslintPluginCdk,
|
|
29
|
+
},
|
|
30
|
+
rules: {
|
|
31
|
+
...eslintPluginCdk.configs.recommended.rules,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### For more detailed documentation, see [docs for eslint-plugin-cdk](https://eslint-plugin-cdk.dev/)
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { noClassInInterfaceProps } from "./no-class-in-interface.mjs";
|
|
2
|
+
import { noConstructStackSuffix } from "./no-construct-stack-suffix.mjs";
|
|
3
|
+
import { noImportPrivate } from "./no-import-private.mjs";
|
|
4
|
+
import { noMutablePropsInterface } from "./no-mutable-props-interface.mjs";
|
|
5
|
+
import { noMutablePublicFields } from "./no-mutable-public-fields.mjs";
|
|
6
|
+
import { noParentNameConstructIdMatch } from "./no-parent-name-construct-id-match.mjs";
|
|
7
|
+
import { noPublicClassFields } from "./no-public-class-fields.mjs";
|
|
8
|
+
import { pascalCaseConstructId } from "./pascal-case-construct-id.mjs";
|
|
9
|
+
var plugin = {
|
|
10
|
+
rules: {
|
|
11
|
+
"no-class-in-interface": noClassInInterfaceProps,
|
|
12
|
+
"no-construct-stack-suffix": noConstructStackSuffix,
|
|
13
|
+
"no-import-private": noImportPrivate,
|
|
14
|
+
"no-parent-name-construct-id-match": noParentNameConstructIdMatch,
|
|
15
|
+
"no-public-class-fields": noPublicClassFields,
|
|
16
|
+
"pascal-case-construct-id": pascalCaseConstructId,
|
|
17
|
+
"no-mutable-public-fields": noMutablePublicFields,
|
|
18
|
+
"no-mutable-props-interface": noMutablePropsInterface,
|
|
19
|
+
},
|
|
20
|
+
configs: {
|
|
21
|
+
recommended: {
|
|
22
|
+
plugins: ["cdk"],
|
|
23
|
+
rules: {
|
|
24
|
+
"cdk/no-class-in-interface": "error",
|
|
25
|
+
"cdk/no-construct-stack-suffix": "error",
|
|
26
|
+
"cdk/no-parent-name-construct-id-match": "error",
|
|
27
|
+
"cdk/no-public-class-fields": "error",
|
|
28
|
+
"cdk/pascal-case-construct-id": "error",
|
|
29
|
+
"cdk/no-mutable-public-fields": "warn",
|
|
30
|
+
"cdk/no-mutable-props-interface": "warn",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export default plugin;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { SymbolFlags } from "typescript";
|
|
3
|
+
export var noClassInInterfaceProps = ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
+
meta: {
|
|
5
|
+
type: "problem",
|
|
6
|
+
docs: {
|
|
7
|
+
description: "Disallow class types in interface properties",
|
|
8
|
+
},
|
|
9
|
+
messages: {
|
|
10
|
+
noClassInInterfaceProps: "Interface property '{{ propertyName }}' should not use class type '{{ typeName }}'. Consider using an interface or type alias instead.",
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
},
|
|
14
|
+
defaultOptions: [],
|
|
15
|
+
create: function (context) {
|
|
16
|
+
var parserServices = ESLintUtils.getParserServices(context);
|
|
17
|
+
var checker = parserServices.program.getTypeChecker();
|
|
18
|
+
return {
|
|
19
|
+
TSInterfaceDeclaration: function (node) {
|
|
20
|
+
for (var _i = 0, _a = node.body.body; _i < _a.length; _i++) {
|
|
21
|
+
var property = _a[_i];
|
|
22
|
+
if (property.type !== "TSPropertySignature" ||
|
|
23
|
+
property.key.type !== "Identifier") {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
var tsNode = parserServices.esTreeNodeToTSNodeMap.get(property);
|
|
27
|
+
var type = checker.getTypeAtLocation(tsNode);
|
|
28
|
+
if (!type.symbol)
|
|
29
|
+
continue;
|
|
30
|
+
// NOTE: check class type
|
|
31
|
+
var isClass = type.symbol.flags === SymbolFlags.Class;
|
|
32
|
+
if (!isClass)
|
|
33
|
+
continue;
|
|
34
|
+
context.report({
|
|
35
|
+
node: property,
|
|
36
|
+
messageId: "noClassInInterfaceProps",
|
|
37
|
+
data: {
|
|
38
|
+
propertyName: property.key.name,
|
|
39
|
+
typeName: type.symbol.name,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { toPascalCase } from "./utils/convertString.mjs";
|
|
3
|
+
export var noConstructStackSuffix = ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
+
meta: {
|
|
5
|
+
type: "problem",
|
|
6
|
+
docs: {
|
|
7
|
+
description: "Effort to avoid using 'Construct' and 'Stack' suffix in construct id.",
|
|
8
|
+
},
|
|
9
|
+
messages: {
|
|
10
|
+
noConstructStackSuffix: "{{ classType }} ID '{{ id }}' should not include {{ suffix }} suffix.",
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
},
|
|
14
|
+
defaultOptions: [],
|
|
15
|
+
create: function (context) {
|
|
16
|
+
return {
|
|
17
|
+
ClassBody: function (node) {
|
|
18
|
+
var _a;
|
|
19
|
+
var parent = node.parent;
|
|
20
|
+
if ((parent === null || parent === void 0 ? void 0 : parent.type) !== "ClassDeclaration")
|
|
21
|
+
return;
|
|
22
|
+
var className = (_a = parent.id) === null || _a === void 0 ? void 0 : _a.name;
|
|
23
|
+
if (!className)
|
|
24
|
+
return;
|
|
25
|
+
for (var _i = 0, _b = node.body; _i < _b.length; _i++) {
|
|
26
|
+
var body = _b[_i];
|
|
27
|
+
if (body.type !== "MethodDefinition" ||
|
|
28
|
+
!["method", "constructor"].includes(body.kind) ||
|
|
29
|
+
body.value.type !== "FunctionExpression") {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
validateConstructorBody(node, body.value, context);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Validate the constructor body for the parent class
|
|
40
|
+
* - validate each statement in the constructor body
|
|
41
|
+
*/
|
|
42
|
+
var validateConstructorBody = function (node, expression, context) {
|
|
43
|
+
var _a;
|
|
44
|
+
for (var _i = 0, _b = expression.body.body; _i < _b.length; _i++) {
|
|
45
|
+
var statement = _b[_i];
|
|
46
|
+
switch (statement.type) {
|
|
47
|
+
case "VariableDeclaration": {
|
|
48
|
+
var newExpression = statement.declarations[0].init;
|
|
49
|
+
if ((newExpression === null || newExpression === void 0 ? void 0 : newExpression.type) !== "NewExpression")
|
|
50
|
+
continue;
|
|
51
|
+
validateConstructId(node, context, newExpression);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case "ExpressionStatement": {
|
|
55
|
+
if (((_a = statement.expression) === null || _a === void 0 ? void 0 : _a.type) !== "NewExpression")
|
|
56
|
+
break;
|
|
57
|
+
validateStatement(node, statement, context);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "IfStatement": {
|
|
61
|
+
traverseStatements(node, statement.consequent, context);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case "SwitchStatement": {
|
|
65
|
+
for (var _c = 0, _d = statement.cases; _c < _d.length; _c++) {
|
|
66
|
+
var switchCase = _d[_c];
|
|
67
|
+
for (var _e = 0, _f = switchCase.consequent; _e < _f.length; _e++) {
|
|
68
|
+
var statement_1 = _f[_e];
|
|
69
|
+
traverseStatements(node, statement_1, context);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Recursively traverse and validate statements in the AST
|
|
79
|
+
* - Handles BlockStatement, ExpressionStatement, and VariableDeclaration
|
|
80
|
+
* - Validates construct IDs
|
|
81
|
+
*/
|
|
82
|
+
var traverseStatements = function (node, statement, context) {
|
|
83
|
+
switch (statement.type) {
|
|
84
|
+
case "BlockStatement": {
|
|
85
|
+
for (var _i = 0, _a = statement.body; _i < _a.length; _i++) {
|
|
86
|
+
var body = _a[_i];
|
|
87
|
+
validateStatement(node, body, context);
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "ExpressionStatement": {
|
|
92
|
+
var newExpression = statement.expression;
|
|
93
|
+
if ((newExpression === null || newExpression === void 0 ? void 0 : newExpression.type) !== "NewExpression")
|
|
94
|
+
break;
|
|
95
|
+
validateStatement(node, statement, context);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case "VariableDeclaration": {
|
|
99
|
+
var newExpression = statement.declarations[0].init;
|
|
100
|
+
if ((newExpression === null || newExpression === void 0 ? void 0 : newExpression.type) !== "NewExpression")
|
|
101
|
+
break;
|
|
102
|
+
validateConstructId(node, context, newExpression);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Validate a single statement in the AST
|
|
109
|
+
* - Handles different types of statements (Variable, Expression, If, Switch)
|
|
110
|
+
* - Extracts and validates construct IDs from new expressions
|
|
111
|
+
*/
|
|
112
|
+
var validateStatement = function (node, body, context) {
|
|
113
|
+
switch (body.type) {
|
|
114
|
+
case "VariableDeclaration": {
|
|
115
|
+
var newExpression = body.declarations[0].init;
|
|
116
|
+
if ((newExpression === null || newExpression === void 0 ? void 0 : newExpression.type) !== "NewExpression")
|
|
117
|
+
break;
|
|
118
|
+
validateConstructId(node, context, newExpression);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case "ExpressionStatement": {
|
|
122
|
+
var newExpression = body.expression;
|
|
123
|
+
if ((newExpression === null || newExpression === void 0 ? void 0 : newExpression.type) !== "NewExpression")
|
|
124
|
+
break;
|
|
125
|
+
validateConstructId(node, context, newExpression);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "IfStatement": {
|
|
129
|
+
validateIfStatement(node, body, context);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "SwitchStatement": {
|
|
133
|
+
validateSwitchStatement(node, body, context);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Validate the `if` statement
|
|
140
|
+
* - Validate recursively if `if` statements are nested
|
|
141
|
+
*/
|
|
142
|
+
var validateIfStatement = function (node, ifStatement, context) {
|
|
143
|
+
traverseStatements(node, ifStatement.consequent, context);
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Validate the `switch` statement
|
|
147
|
+
* - Validate recursively if `switch` statements are nested
|
|
148
|
+
*/
|
|
149
|
+
var validateSwitchStatement = function (node, switchStatement, context) {
|
|
150
|
+
for (var _i = 0, _a = switchStatement.cases; _i < _a.length; _i++) {
|
|
151
|
+
var statement = _a[_i];
|
|
152
|
+
for (var _b = 0, _c = statement.consequent; _b < _c.length; _b++) {
|
|
153
|
+
var _consequent = _c[_b];
|
|
154
|
+
traverseStatements(node, _consequent, context);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Validate that construct ID does not end with "Construct" or "Stack"
|
|
160
|
+
*/
|
|
161
|
+
var validateConstructId = function (node, context, expression) {
|
|
162
|
+
if (expression.arguments.length < 2)
|
|
163
|
+
return;
|
|
164
|
+
// NOTE: Treat the second argument as ID
|
|
165
|
+
var secondArg = expression.arguments[1];
|
|
166
|
+
if (secondArg.type !== "Literal" || typeof secondArg.value !== "string") {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
var formattedConstructId = toPascalCase(secondArg.value);
|
|
170
|
+
if (formattedConstructId.endsWith("Construct")) {
|
|
171
|
+
context.report({
|
|
172
|
+
node: node,
|
|
173
|
+
messageId: "noConstructStackSuffix",
|
|
174
|
+
data: {
|
|
175
|
+
classType: "Construct",
|
|
176
|
+
id: secondArg.value,
|
|
177
|
+
suffix: "Construct",
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
else if (formattedConstructId.endsWith("Stack")) {
|
|
182
|
+
context.report({
|
|
183
|
+
node: node,
|
|
184
|
+
messageId: "noConstructStackSuffix",
|
|
185
|
+
data: {
|
|
186
|
+
classType: "Stack",
|
|
187
|
+
id: secondArg.value,
|
|
188
|
+
suffix: "Stack",
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
/**
|
|
3
|
+
* Split the directory path into segments (split at `/`)
|
|
4
|
+
* @param dirPath - The directory path to split
|
|
5
|
+
* @returns The segments of the directory path
|
|
6
|
+
*/
|
|
7
|
+
var getDirSegments = function (dirPath) {
|
|
8
|
+
return dirPath.split(path.sep).filter(function (segment) { return segment !== ""; });
|
|
9
|
+
};
|
|
10
|
+
export var noImportPrivate = {
|
|
11
|
+
meta: {
|
|
12
|
+
type: "problem",
|
|
13
|
+
docs: {
|
|
14
|
+
description: "Cannot import modules from private dir at different levels of the hierarchy.",
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
noImportPrivate: "Cannot import modules from private dir at different levels of the hierarchy.",
|
|
18
|
+
},
|
|
19
|
+
schema: [],
|
|
20
|
+
},
|
|
21
|
+
create: function (context) {
|
|
22
|
+
return {
|
|
23
|
+
ImportDeclaration: function (node) {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
var importPath = (_b = (_a = node.source.value) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : "";
|
|
26
|
+
var currentFilePath = context.filename;
|
|
27
|
+
var currentDirPath = path.dirname(currentFilePath);
|
|
28
|
+
if (importPath.includes("/private")) {
|
|
29
|
+
var absoluteCurrentDirPath = path.resolve(currentDirPath);
|
|
30
|
+
var absoluteImportPath = path.resolve(currentDirPath, importPath);
|
|
31
|
+
// NOTE: Get the directory from the import path up to the private directory
|
|
32
|
+
var importDirBeforePrivate = absoluteImportPath.split("/private")[0];
|
|
33
|
+
var currentDirSegments = getDirSegments(absoluteCurrentDirPath);
|
|
34
|
+
var importDirSegments_1 = getDirSegments(importDirBeforePrivate);
|
|
35
|
+
if (currentDirSegments.length !== importDirSegments_1.length ||
|
|
36
|
+
currentDirSegments.some(function (segment, index) { return segment !== importDirSegments_1[index]; })) {
|
|
37
|
+
context.report({ node: node, messageId: "noImportPrivate" });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
export var noMutablePropsInterface = ESLintUtils.RuleCreator.withoutDocs({
|
|
3
|
+
meta: {
|
|
4
|
+
type: "problem",
|
|
5
|
+
docs: {
|
|
6
|
+
description: "Disallow mutable properties in Props interfaces",
|
|
7
|
+
},
|
|
8
|
+
fixable: "code",
|
|
9
|
+
messages: {
|
|
10
|
+
noMutablePropsInterface: "Property '{{ propertyName }}' in Props interface should be readonly.",
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
},
|
|
14
|
+
defaultOptions: [],
|
|
15
|
+
create: function (context) {
|
|
16
|
+
var sourceCode = context.sourceCode;
|
|
17
|
+
return {
|
|
18
|
+
TSInterfaceDeclaration: function (node) {
|
|
19
|
+
// NOTE: Interface name check for "Props"
|
|
20
|
+
if (!node.id.name.endsWith("Props"))
|
|
21
|
+
return;
|
|
22
|
+
var _loop_1 = function (property) {
|
|
23
|
+
if (property.type !== "TSPropertySignature" ||
|
|
24
|
+
property.key.type !== "Identifier") {
|
|
25
|
+
return "continue";
|
|
26
|
+
}
|
|
27
|
+
// NOTE: Skip if already readonly
|
|
28
|
+
if (property.readonly)
|
|
29
|
+
return "continue";
|
|
30
|
+
context.report({
|
|
31
|
+
node: property,
|
|
32
|
+
messageId: "noMutablePropsInterface",
|
|
33
|
+
data: {
|
|
34
|
+
propertyName: property.key.name,
|
|
35
|
+
},
|
|
36
|
+
fix: function (fixer) {
|
|
37
|
+
var propertyText = sourceCode.getText(property);
|
|
38
|
+
return fixer.replaceText(property, "readonly ".concat(propertyText));
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
for (var _i = 0, _a = node.body.body; _i < _a.length; _i++) {
|
|
43
|
+
var property = _a[_i];
|
|
44
|
+
_loop_1(property);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
export var noMutablePublicFields = ESLintUtils.RuleCreator.withoutDocs({
|
|
3
|
+
meta: {
|
|
4
|
+
type: "problem",
|
|
5
|
+
docs: {
|
|
6
|
+
description: "Disallow mutable public class fields",
|
|
7
|
+
},
|
|
8
|
+
fixable: "code",
|
|
9
|
+
messages: {
|
|
10
|
+
noMutablePublicFields: "Public field '{{ propertyName }}' should be readonly. Consider adding the 'readonly' modifier.",
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
},
|
|
14
|
+
defaultOptions: [],
|
|
15
|
+
create: function (context) {
|
|
16
|
+
var sourceCode = context.sourceCode;
|
|
17
|
+
return {
|
|
18
|
+
ClassDeclaration: function (node) {
|
|
19
|
+
var _a;
|
|
20
|
+
var _loop_1 = function (member) {
|
|
21
|
+
if (member.type !== "PropertyDefinition" ||
|
|
22
|
+
member.key.type !== "Identifier") {
|
|
23
|
+
return "continue";
|
|
24
|
+
}
|
|
25
|
+
// NOTE: Skip private and protected fields
|
|
26
|
+
if (["private", "protected"].includes((_a = member.accessibility) !== null && _a !== void 0 ? _a : "")) {
|
|
27
|
+
return "continue";
|
|
28
|
+
}
|
|
29
|
+
// NOTE: Skip if readonly is present
|
|
30
|
+
if (member.readonly)
|
|
31
|
+
return "continue";
|
|
32
|
+
context.report({
|
|
33
|
+
node: member,
|
|
34
|
+
messageId: "noMutablePublicFields",
|
|
35
|
+
data: {
|
|
36
|
+
propertyName: member.key.name,
|
|
37
|
+
},
|
|
38
|
+
fix: function (fixer) {
|
|
39
|
+
var accessibility = member.accessibility ? "public " : "";
|
|
40
|
+
var paramText = sourceCode.getText(member);
|
|
41
|
+
var _a = paramText.split(":"), key = _a[0], value = _a[1];
|
|
42
|
+
var replacedKey = key.startsWith("public ")
|
|
43
|
+
? key.replace("public ", "")
|
|
44
|
+
: key;
|
|
45
|
+
return fixer.replaceText(member, "".concat(accessibility, "readonly ").concat(replacedKey, ":").concat(value));
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
for (var _i = 0, _b = node.body.body; _i < _b.length; _i++) {
|
|
50
|
+
var member = _b[_i];
|
|
51
|
+
_loop_1(member);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { toPascalCase } from "./utils/convertString.mjs";
|
|
3
|
+
export var noParentNameConstructIdMatch = ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
+
meta: {
|
|
5
|
+
type: "problem",
|
|
6
|
+
docs: {
|
|
7
|
+
description: "Enforce that construct IDs does not match the parent construct name.",
|
|
8
|
+
},
|
|
9
|
+
messages: {
|
|
10
|
+
noParentNameConstructIdMatch: "Construct ID '{{ constructId }}' should not match parent construct name '{{ parentConstructName }}'. Use a more specific identifier.",
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
},
|
|
14
|
+
defaultOptions: [],
|
|
15
|
+
create: function (context) {
|
|
16
|
+
return {
|
|
17
|
+
ClassBody: function (node) {
|
|
18
|
+
var _a;
|
|
19
|
+
var parent = node.parent;
|
|
20
|
+
if ((parent === null || parent === void 0 ? void 0 : parent.type) !== "ClassDeclaration")
|
|
21
|
+
return;
|
|
22
|
+
var parentClassName = (_a = parent.id) === null || _a === void 0 ? void 0 : _a.name;
|
|
23
|
+
if (!parentClassName)
|
|
24
|
+
return;
|
|
25
|
+
for (var _i = 0, _b = node.body; _i < _b.length; _i++) {
|
|
26
|
+
var body = _b[_i];
|
|
27
|
+
if (body.type !== "MethodDefinition" ||
|
|
28
|
+
!["method", "constructor"].includes(body.kind) ||
|
|
29
|
+
body.value.type !== "FunctionExpression") {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
validateConstructorBody({
|
|
33
|
+
node: node,
|
|
34
|
+
expression: body.value,
|
|
35
|
+
parentClassName: parentClassName,
|
|
36
|
+
context: context,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Validate the constructor body for the parent class
|
|
45
|
+
* - validate each statement in the constructor body
|
|
46
|
+
*/
|
|
47
|
+
var validateConstructorBody = function (_a) {
|
|
48
|
+
var _b;
|
|
49
|
+
var node = _a.node, expression = _a.expression, parentClassName = _a.parentClassName, context = _a.context;
|
|
50
|
+
for (var _i = 0, _c = expression.body.body; _i < _c.length; _i++) {
|
|
51
|
+
var statement = _c[_i];
|
|
52
|
+
switch (statement.type) {
|
|
53
|
+
case "VariableDeclaration": {
|
|
54
|
+
var newExpression = statement.declarations[0].init;
|
|
55
|
+
if ((newExpression === null || newExpression === void 0 ? void 0 : newExpression.type) !== "NewExpression")
|
|
56
|
+
continue;
|
|
57
|
+
validateConstructId({
|
|
58
|
+
node: node,
|
|
59
|
+
context: context,
|
|
60
|
+
expression: newExpression,
|
|
61
|
+
parentClassName: parentClassName,
|
|
62
|
+
});
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case "ExpressionStatement": {
|
|
66
|
+
if (((_b = statement.expression) === null || _b === void 0 ? void 0 : _b.type) !== "NewExpression")
|
|
67
|
+
break;
|
|
68
|
+
validateStatement({
|
|
69
|
+
node: node,
|
|
70
|
+
body: statement,
|
|
71
|
+
parentClassName: parentClassName,
|
|
72
|
+
context: context,
|
|
73
|
+
});
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "IfStatement": {
|
|
77
|
+
traverseStatements({
|
|
78
|
+
node: node,
|
|
79
|
+
context: context,
|
|
80
|
+
parentClassName: parentClassName,
|
|
81
|
+
statement: statement.consequent,
|
|
82
|
+
});
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "SwitchStatement": {
|
|
86
|
+
for (var _d = 0, _e = statement.cases; _d < _e.length; _d++) {
|
|
87
|
+
var switchCase = _e[_d];
|
|
88
|
+
for (var _f = 0, _g = switchCase.consequent; _f < _g.length; _f++) {
|
|
89
|
+
var statement_1 = _g[_f];
|
|
90
|
+
traverseStatements({
|
|
91
|
+
node: node,
|
|
92
|
+
context: context,
|
|
93
|
+
parentClassName: parentClassName,
|
|
94
|
+
statement: statement_1,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Recursively traverse and validate statements in the AST
|
|
105
|
+
* - Handles BlockStatement, ExpressionStatement, and VariableDeclaration
|
|
106
|
+
* - Validates construct IDs against parent class name
|
|
107
|
+
*/
|
|
108
|
+
var traverseStatements = function (_a) {
|
|
109
|
+
var node = _a.node, statement = _a.statement, parentClassName = _a.parentClassName, context = _a.context;
|
|
110
|
+
switch (statement.type) {
|
|
111
|
+
case "BlockStatement": {
|
|
112
|
+
for (var _i = 0, _b = statement.body; _i < _b.length; _i++) {
|
|
113
|
+
var body = _b[_i];
|
|
114
|
+
validateStatement({
|
|
115
|
+
node: node,
|
|
116
|
+
body: body,
|
|
117
|
+
parentClassName: parentClassName,
|
|
118
|
+
context: context,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "ExpressionStatement": {
|
|
124
|
+
var newExpression = statement.expression;
|
|
125
|
+
if ((newExpression === null || newExpression === void 0 ? void 0 : newExpression.type) !== "NewExpression")
|
|
126
|
+
break;
|
|
127
|
+
validateStatement({
|
|
128
|
+
node: node,
|
|
129
|
+
body: statement,
|
|
130
|
+
parentClassName: parentClassName,
|
|
131
|
+
context: context,
|
|
132
|
+
});
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case "VariableDeclaration": {
|
|
136
|
+
var newExpression = statement.declarations[0].init;
|
|
137
|
+
if ((newExpression === null || newExpression === void 0 ? void 0 : newExpression.type) !== "NewExpression")
|
|
138
|
+
break;
|
|
139
|
+
validateConstructId({
|
|
140
|
+
node: node,
|
|
141
|
+
context: context,
|
|
142
|
+
expression: newExpression,
|
|
143
|
+
parentClassName: parentClassName,
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Validate a single statement in the AST
|
|
151
|
+
* - Handles different types of statements (Variable, Expression, If, Switch)
|
|
152
|
+
* - Extracts and validates construct IDs from new expressions
|
|
153
|
+
*/
|
|
154
|
+
var validateStatement = function (_a) {
|
|
155
|
+
var node = _a.node, body = _a.body, parentClassName = _a.parentClassName, context = _a.context;
|
|
156
|
+
switch (body.type) {
|
|
157
|
+
case "VariableDeclaration": {
|
|
158
|
+
var newExpression = body.declarations[0].init;
|
|
159
|
+
if ((newExpression === null || newExpression === void 0 ? void 0 : newExpression.type) !== "NewExpression")
|
|
160
|
+
break;
|
|
161
|
+
validateConstructId({
|
|
162
|
+
node: node,
|
|
163
|
+
context: context,
|
|
164
|
+
expression: newExpression,
|
|
165
|
+
parentClassName: parentClassName,
|
|
166
|
+
});
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case "ExpressionStatement": {
|
|
170
|
+
var newExpression = body.expression;
|
|
171
|
+
if ((newExpression === null || newExpression === void 0 ? void 0 : newExpression.type) !== "NewExpression")
|
|
172
|
+
break;
|
|
173
|
+
validateConstructId({
|
|
174
|
+
node: node,
|
|
175
|
+
context: context,
|
|
176
|
+
expression: newExpression,
|
|
177
|
+
parentClassName: parentClassName,
|
|
178
|
+
});
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case "IfStatement": {
|
|
182
|
+
validateIfStatement({
|
|
183
|
+
node: node,
|
|
184
|
+
ifStatement: body,
|
|
185
|
+
parentClassName: parentClassName,
|
|
186
|
+
context: context,
|
|
187
|
+
});
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case "SwitchStatement": {
|
|
191
|
+
validateSwitchStatement({
|
|
192
|
+
node: node,
|
|
193
|
+
switchStatement: body,
|
|
194
|
+
parentClassName: parentClassName,
|
|
195
|
+
context: context,
|
|
196
|
+
});
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Validate the `if` statement
|
|
203
|
+
* - Validate recursively if `if` statements are nested
|
|
204
|
+
*/
|
|
205
|
+
var validateIfStatement = function (_a) {
|
|
206
|
+
var node = _a.node, ifStatement = _a.ifStatement, parentClassName = _a.parentClassName, context = _a.context;
|
|
207
|
+
traverseStatements({
|
|
208
|
+
node: node,
|
|
209
|
+
context: context,
|
|
210
|
+
parentClassName: parentClassName,
|
|
211
|
+
statement: ifStatement.consequent,
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Validate the `switch` statement
|
|
216
|
+
* - Validate recursively if `switch` statements are nested
|
|
217
|
+
*/
|
|
218
|
+
var validateSwitchStatement = function (_a) {
|
|
219
|
+
var node = _a.node, switchStatement = _a.switchStatement, parentClassName = _a.parentClassName, context = _a.context;
|
|
220
|
+
for (var _i = 0, _b = switchStatement.cases; _i < _b.length; _i++) {
|
|
221
|
+
var statement = _b[_i];
|
|
222
|
+
for (var _c = 0, _d = statement.consequent; _c < _d.length; _c++) {
|
|
223
|
+
var _consequent = _d[_c];
|
|
224
|
+
traverseStatements({
|
|
225
|
+
node: node,
|
|
226
|
+
context: context,
|
|
227
|
+
parentClassName: parentClassName,
|
|
228
|
+
statement: _consequent,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Validate that parent construct name and child id do not match
|
|
235
|
+
*/
|
|
236
|
+
var validateConstructId = function (_a) {
|
|
237
|
+
var node = _a.node, context = _a.context, expression = _a.expression, parentClassName = _a.parentClassName;
|
|
238
|
+
if (expression.arguments.length < 2)
|
|
239
|
+
return;
|
|
240
|
+
// NOTE: Treat the second argument as ID
|
|
241
|
+
var secondArg = expression.arguments[1];
|
|
242
|
+
if (secondArg.type !== "Literal" || typeof secondArg.value !== "string") {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
var formattedConstructId = toPascalCase(secondArg.value);
|
|
246
|
+
var formattedParentClassName = toPascalCase(parentClassName);
|
|
247
|
+
if (formattedParentClassName === formattedConstructId) {
|
|
248
|
+
context.report({
|
|
249
|
+
node: node,
|
|
250
|
+
messageId: "noParentNameConstructIdMatch",
|
|
251
|
+
data: {
|
|
252
|
+
constructId: secondArg.value,
|
|
253
|
+
parentConstructName: parentClassName,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { ESLintUtils, } from "@typescript-eslint/utils";
|
|
2
|
+
import { SymbolFlags } from "typescript";
|
|
3
|
+
export var noPublicClassFields = ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
+
meta: {
|
|
5
|
+
type: "problem",
|
|
6
|
+
docs: {
|
|
7
|
+
description: "Disallow class types in public class fields",
|
|
8
|
+
},
|
|
9
|
+
messages: {
|
|
10
|
+
noPublicClassFields: "Public field '{{ propertyName }}' should not use class type '{{ typeName }}'. Consider using an interface or type alias instead.",
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
},
|
|
14
|
+
defaultOptions: [],
|
|
15
|
+
create: function (context) {
|
|
16
|
+
var parserServices = ESLintUtils.getParserServices(context);
|
|
17
|
+
var typeChecker = parserServices.program.getTypeChecker();
|
|
18
|
+
return {
|
|
19
|
+
ClassDeclaration: function (node) {
|
|
20
|
+
// NOTE: Check class members
|
|
21
|
+
validateClassMember({
|
|
22
|
+
node: node,
|
|
23
|
+
context: context,
|
|
24
|
+
parserServices: parserServices,
|
|
25
|
+
typeChecker: typeChecker,
|
|
26
|
+
});
|
|
27
|
+
// NOTE: Check constructor parameter properties
|
|
28
|
+
var constructor = node.body.body.find(function (member) {
|
|
29
|
+
return member.type === "MethodDefinition" && member.kind === "constructor";
|
|
30
|
+
});
|
|
31
|
+
if (!constructor || constructor.value.type !== "FunctionExpression") {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
validateConstructorParameterProperty({
|
|
35
|
+
constructor: constructor,
|
|
36
|
+
context: context,
|
|
37
|
+
parserServices: parserServices,
|
|
38
|
+
typeChecker: typeChecker,
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
var validateClassMember = function (_a) {
|
|
45
|
+
var _b;
|
|
46
|
+
var node = _a.node, context = _a.context, parserServices = _a.parserServices, typeChecker = _a.typeChecker;
|
|
47
|
+
for (var _i = 0, _c = node.body.body; _i < _c.length; _i++) {
|
|
48
|
+
var member = _c[_i];
|
|
49
|
+
if (member.type !== "PropertyDefinition" ||
|
|
50
|
+
member.key.type !== "Identifier") {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// NOTE: Skip private and protected fields
|
|
54
|
+
if (["private", "protected"].includes((_b = member.accessibility) !== null && _b !== void 0 ? _b : "")) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// NOTE: Skip fields without type annotation
|
|
58
|
+
if (!member.typeAnnotation) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
var tsNode = parserServices.esTreeNodeToTSNodeMap.get(member);
|
|
62
|
+
var type = typeChecker.getTypeAtLocation(tsNode);
|
|
63
|
+
if (!type.symbol)
|
|
64
|
+
continue;
|
|
65
|
+
var isClass = type.symbol.flags === SymbolFlags.Class;
|
|
66
|
+
if (!isClass)
|
|
67
|
+
continue;
|
|
68
|
+
context.report({
|
|
69
|
+
node: member,
|
|
70
|
+
messageId: "noPublicClassFields",
|
|
71
|
+
data: {
|
|
72
|
+
propertyName: member.key.name,
|
|
73
|
+
typeName: type.symbol.name,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var validateConstructorParameterProperty = function (_a) {
|
|
79
|
+
var _b;
|
|
80
|
+
var constructor = _a.constructor, context = _a.context, parserServices = _a.parserServices, typeChecker = _a.typeChecker;
|
|
81
|
+
for (var _i = 0, _c = constructor.value.params; _i < _c.length; _i++) {
|
|
82
|
+
var param = _c[_i];
|
|
83
|
+
if (param.type !== "TSParameterProperty" ||
|
|
84
|
+
param.parameter.type !== "Identifier") {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
// NOTE: Skip private and protected parameters
|
|
88
|
+
if (["private", "protected"].includes((_b = param.accessibility) !== null && _b !== void 0 ? _b : "")) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
// NOTE: Skip parameters without type annotation
|
|
92
|
+
if (!param.parameter.typeAnnotation) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
var tsNode = parserServices.esTreeNodeToTSNodeMap.get(param);
|
|
96
|
+
var type = typeChecker.getTypeAtLocation(tsNode);
|
|
97
|
+
if (!type.symbol)
|
|
98
|
+
continue;
|
|
99
|
+
var isClass = type.symbol.flags === SymbolFlags.Class;
|
|
100
|
+
if (!isClass)
|
|
101
|
+
continue;
|
|
102
|
+
context.report({
|
|
103
|
+
node: param,
|
|
104
|
+
messageId: "noPublicClassFields",
|
|
105
|
+
data: {
|
|
106
|
+
propertyName: param.parameter.name,
|
|
107
|
+
typeName: type.symbol.name,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { toPascalCase } from "./utils/convertString.mjs";
|
|
2
|
+
var QUOTE_TYPE = {
|
|
3
|
+
SINGLE: "'",
|
|
4
|
+
DOUBLE: '"',
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* check if the string is PascalCase
|
|
8
|
+
* @param str - The string to check
|
|
9
|
+
* @returns true if the string is PascalCase, false otherwise
|
|
10
|
+
*/
|
|
11
|
+
var isPascalCase = function (str) {
|
|
12
|
+
return /^[A-Z][a-zA-Z0-9]*$/.test(str);
|
|
13
|
+
};
|
|
14
|
+
var validateConstructId = function (node, context, args) {
|
|
15
|
+
var _a;
|
|
16
|
+
if (args.length < 2)
|
|
17
|
+
return;
|
|
18
|
+
// NOTE: Treat the second argument as ID
|
|
19
|
+
var secondArg = args[1];
|
|
20
|
+
if (secondArg.type !== "Literal" || typeof secondArg.value !== "string") {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
var quote = ((_a = secondArg.raw) === null || _a === void 0 ? void 0 : _a.startsWith('"'))
|
|
24
|
+
? QUOTE_TYPE.DOUBLE
|
|
25
|
+
: QUOTE_TYPE.SINGLE;
|
|
26
|
+
if (!isPascalCase(secondArg.value)) {
|
|
27
|
+
context.report({
|
|
28
|
+
node: node,
|
|
29
|
+
messageId: "pascalCaseConstructId",
|
|
30
|
+
fix: function (fixer) {
|
|
31
|
+
var pascalCaseValue = toPascalCase(secondArg.value);
|
|
32
|
+
return fixer.replaceText(secondArg, "".concat(quote).concat(pascalCaseValue).concat(quote));
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
export var pascalCaseConstructId = {
|
|
38
|
+
meta: {
|
|
39
|
+
type: "problem",
|
|
40
|
+
docs: {
|
|
41
|
+
description: "Enforce PascalCase for Construct ID.",
|
|
42
|
+
},
|
|
43
|
+
messages: {
|
|
44
|
+
pascalCaseConstructId: "Construct ID must be PascalCase.",
|
|
45
|
+
},
|
|
46
|
+
schema: [],
|
|
47
|
+
fixable: "code",
|
|
48
|
+
},
|
|
49
|
+
create: function (context) {
|
|
50
|
+
return {
|
|
51
|
+
ExpressionStatement: function (node) {
|
|
52
|
+
if (node.expression.type !== "NewExpression")
|
|
53
|
+
return;
|
|
54
|
+
validateConstructId(node, context, node.expression.arguments);
|
|
55
|
+
},
|
|
56
|
+
VariableDeclaration: function (node) {
|
|
57
|
+
var _a;
|
|
58
|
+
if (!node.declarations.length)
|
|
59
|
+
return;
|
|
60
|
+
for (var _i = 0, _b = node.declarations; _i < _b.length; _i++) {
|
|
61
|
+
var declaration = _b[_i];
|
|
62
|
+
if (((_a = declaration.init) === null || _a === void 0 ? void 0 : _a.type) !== "NewExpression")
|
|
63
|
+
return;
|
|
64
|
+
validateConstructId(node, context, declaration.init.arguments);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a string to PascalCase
|
|
3
|
+
* @param str - The string to convert
|
|
4
|
+
* @returns The PascalCase string
|
|
5
|
+
*/
|
|
6
|
+
export var toPascalCase = function (str) {
|
|
7
|
+
return str
|
|
8
|
+
.split(/[-_\s]/)
|
|
9
|
+
.map(function (word) {
|
|
10
|
+
// Consider camelCase, split by uppercase letters
|
|
11
|
+
return word
|
|
12
|
+
.replace(/([A-Z])/g, " $1")
|
|
13
|
+
.split(/\s+/)
|
|
14
|
+
.map(function (part) { return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase(); })
|
|
15
|
+
.join("");
|
|
16
|
+
})
|
|
17
|
+
.join("");
|
|
18
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-cdk-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "eslint plugin for AWS CDK projects",
|
|
5
|
+
"main": "dist/index.mjs",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "rm -rf dist && tsc src/*.*ts --outDir dist --skipLibCheck",
|
|
9
|
+
"test": "vitest --run",
|
|
10
|
+
"lint": "eslint --fix --config eslint.config.mjs",
|
|
11
|
+
"release:minor": "standard-version --release-as minor",
|
|
12
|
+
"release:major": "standard-version --release-as major",
|
|
13
|
+
"release:patch": "standard-version --release-as patch",
|
|
14
|
+
"docs:dev": "cd ./docs && pnpm install && pnpm run dev",
|
|
15
|
+
"docs:build": "cd ./docs && pnpm install && pnpm run build",
|
|
16
|
+
"docs:preview": "cd ./docs && pnpm install && pnpm run preview"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@eslint/js": "^9.15.0",
|
|
20
|
+
"@types/estree": "^1.0.6",
|
|
21
|
+
"@types/node": "^22.9.0",
|
|
22
|
+
"@typescript-eslint/rule-tester": "^8.14.0",
|
|
23
|
+
"eslint-plugin-import": "^2.31.0",
|
|
24
|
+
"standard-version": "^9.5.0",
|
|
25
|
+
"typescript": "^5.6.3",
|
|
26
|
+
"typescript-eslint": "^8.14.0",
|
|
27
|
+
"vitest": "^2.1.5"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@typescript-eslint/utils": "^8.14.0",
|
|
31
|
+
"eslint": "9.10.0"
|
|
32
|
+
},
|
|
33
|
+
"volta": {
|
|
34
|
+
"node": "22.11.0"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"keywords": [
|
|
42
|
+
"eslint",
|
|
43
|
+
"eslintplugin",
|
|
44
|
+
"eslint-plugin",
|
|
45
|
+
"aws",
|
|
46
|
+
"cdk"
|
|
47
|
+
],
|
|
48
|
+
"homepage": "https://eslint-plugin-cdk.dev/",
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://github.com/ren-yamanashi/eslint-plugin-cdk.git"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
55
|
+
},
|
|
56
|
+
"author": {
|
|
57
|
+
"name": "ren-yamanashi"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"license": "MIT"
|
|
63
|
+
}
|