@mionjs/devtools 0.8.6 → 0.8.8
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/build/eslint/cjs/index.cjs.map +1 -1
- package/build/eslint/cjs/src/eslint/index.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/index.d.ts.map +1 -0
- package/build/eslint/cjs/src/eslint/rules/enforce-type-imports.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/rules/enforce-type-imports.d.ts.map +1 -0
- package/build/eslint/cjs/src/eslint/rules/formatTypeNames.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/rules/formatTypeNames.d.ts.map +1 -0
- package/build/eslint/cjs/src/eslint/rules/no-mixed-union-properties.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/rules/no-mixed-union-properties.d.ts.map +1 -0
- package/build/eslint/cjs/src/eslint/rules/no-type-imports.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/rules/no-type-imports.d.ts.map +1 -0
- package/build/eslint/cjs/src/eslint/rules/no-typeof-runtype.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/rules/no-typeof-runtype.d.ts.map +1 -0
- package/build/eslint/cjs/src/eslint/rules/no-unreachable-union-types.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/rules/no-unreachable-union-types.d.ts.map +1 -0
- package/build/eslint/cjs/src/eslint/rules/no-vite-client.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/rules/no-vite-client.d.ts.map +1 -0
- package/build/eslint/cjs/src/eslint/rules/pure-functions.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/rules/pure-functions.d.ts.map +1 -0
- package/build/eslint/cjs/src/eslint/rules/strong-typed-routes.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/rules/strong-typed-routes.d.ts.map +1 -0
- package/build/eslint/cjs/src/eslint/rules/type-formats-imports.d.ts +1 -0
- package/build/eslint/cjs/src/eslint/rules/type-formats-imports.d.ts.map +1 -0
- package/build/eslint/cjs/src/pureFns/purityRules.d.ts +1 -0
- package/build/eslint/cjs/src/pureFns/purityRules.d.ts.map +1 -0
- package/build/eslint/esm/index.js.map +1 -1
- package/build/eslint/esm/src/eslint/index.d.ts +1 -0
- package/build/eslint/esm/src/eslint/index.d.ts.map +1 -0
- package/build/eslint/esm/src/eslint/rules/enforce-type-imports.d.ts +1 -0
- package/build/eslint/esm/src/eslint/rules/enforce-type-imports.d.ts.map +1 -0
- package/build/eslint/esm/src/eslint/rules/formatTypeNames.d.ts +1 -0
- package/build/eslint/esm/src/eslint/rules/formatTypeNames.d.ts.map +1 -0
- package/build/eslint/esm/src/eslint/rules/no-mixed-union-properties.d.ts +1 -0
- package/build/eslint/esm/src/eslint/rules/no-mixed-union-properties.d.ts.map +1 -0
- package/build/eslint/esm/src/eslint/rules/no-type-imports.d.ts +1 -0
- package/build/eslint/esm/src/eslint/rules/no-type-imports.d.ts.map +1 -0
- package/build/eslint/esm/src/eslint/rules/no-typeof-runtype.d.ts +1 -0
- package/build/eslint/esm/src/eslint/rules/no-typeof-runtype.d.ts.map +1 -0
- package/build/eslint/esm/src/eslint/rules/no-unreachable-union-types.d.ts +1 -0
- package/build/eslint/esm/src/eslint/rules/no-unreachable-union-types.d.ts.map +1 -0
- package/build/eslint/esm/src/eslint/rules/no-vite-client.d.ts +1 -0
- package/build/eslint/esm/src/eslint/rules/no-vite-client.d.ts.map +1 -0
- package/build/eslint/esm/src/eslint/rules/pure-functions.d.ts +1 -0
- package/build/eslint/esm/src/eslint/rules/pure-functions.d.ts.map +1 -0
- package/build/eslint/esm/src/eslint/rules/strong-typed-routes.d.ts +1 -0
- package/build/eslint/esm/src/eslint/rules/strong-typed-routes.d.ts.map +1 -0
- package/build/eslint/esm/src/eslint/rules/type-formats-imports.d.ts +1 -0
- package/build/eslint/esm/src/eslint/rules/type-formats-imports.d.ts.map +1 -0
- package/build/eslint/esm/src/pureFns/purityRules.d.ts +1 -0
- package/build/eslint/esm/src/pureFns/purityRules.d.ts.map +1 -0
- package/build/vite-plugin/cjs/index.cjs +0 -1
- package/build/vite-plugin/cjs/index.cjs.map +1 -1
- package/build/vite-plugin/cjs/src/pureFns/purityRules.d.ts +1 -0
- package/build/vite-plugin/cjs/src/pureFns/purityRules.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/aotCacheGenerator.d.ts +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/aotCacheGenerator.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/aotDiskCache.d.ts +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/aotDiskCache.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/cjsPackageJsonPlugin.d.ts +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/cjsPackageJsonPlugin.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/constants.d.ts +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/constants.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/extractPureFn.d.ts +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/extractPureFn.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/index.cjs +0 -1
- package/build/vite-plugin/cjs/src/vite-plugin/index.cjs.map +1 -1
- package/build/vite-plugin/cjs/src/vite-plugin/index.d.ts +2 -1
- package/build/vite-plugin/cjs/src/vite-plugin/index.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/mionVitePlugin.cjs.map +1 -1
- package/build/vite-plugin/cjs/src/vite-plugin/mionVitePlugin.d.ts +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/mionVitePlugin.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/resolveModule.d.ts +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/resolveModule.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/transformers.d.ts +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/transformers.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/types.d.ts +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/types.d.ts.map +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/virtualModule.d.ts +1 -0
- package/build/vite-plugin/cjs/src/vite-plugin/virtualModule.d.ts.map +1 -0
- package/build/vite-plugin/esm/index.js +2 -3
- package/build/vite-plugin/esm/src/pureFns/purityRules.d.ts +1 -0
- package/build/vite-plugin/esm/src/pureFns/purityRules.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/aotCacheGenerator.d.ts +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/aotCacheGenerator.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/aotDiskCache.d.ts +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/aotDiskCache.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/cjsPackageJsonPlugin.d.ts +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/cjsPackageJsonPlugin.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/constants.d.ts +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/constants.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/extractPureFn.d.ts +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/extractPureFn.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/index.d.ts +2 -1
- package/build/vite-plugin/esm/src/vite-plugin/index.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/index.js +2 -3
- package/build/vite-plugin/esm/src/vite-plugin/mionVitePlugin.d.ts +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/mionVitePlugin.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/mionVitePlugin.js.map +1 -1
- package/build/vite-plugin/esm/src/vite-plugin/resolveModule.d.ts +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/resolveModule.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/transformers.d.ts +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/transformers.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/types.d.ts +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/types.d.ts.map +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/virtualModule.d.ts +1 -0
- package/build/vite-plugin/esm/src/vite-plugin/virtualModule.d.ts.map +1 -0
- package/package.json +6 -4
- package/src/eslint/index.ts +53 -0
- package/src/eslint/rules/enforce-type-imports.ts +157 -0
- package/src/eslint/rules/formatTypeNames.ts +92 -0
- package/src/eslint/rules/no-mixed-union-properties.ts +276 -0
- package/src/eslint/rules/no-type-imports.ts +386 -0
- package/src/eslint/rules/no-typeof-runtype.ts +112 -0
- package/src/eslint/rules/no-unreachable-union-types.ts +357 -0
- package/src/eslint/rules/no-vite-client.ts +101 -0
- package/src/eslint/rules/pure-functions.ts +523 -0
- package/src/eslint/rules/strong-typed-routes.ts +537 -0
- package/src/eslint/rules/type-formats-imports.ts +73 -0
- package/src/pureFns/purityRules.ts +76 -0
- package/src/vite-plugin/README.md +399 -0
- package/src/vite-plugin/aotCacheGenerator.ts +397 -0
- package/src/vite-plugin/aotDiskCache.ts +193 -0
- package/src/vite-plugin/cjsPackageJsonPlugin.ts +21 -0
- package/src/vite-plugin/constants.ts +50 -0
- package/src/vite-plugin/extractPureFn.ts +845 -0
- package/src/vite-plugin/index.ts +27 -0
- package/src/vite-plugin/mionVitePlugin.ts +738 -0
- package/src/vite-plugin/resolveModule.ts +50 -0
- package/src/vite-plugin/transformers.ts +209 -0
- package/src/vite-plugin/types.ts +182 -0
- package/src/vite-plugin/virtualModule.ts +94 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/* ########
|
|
2
|
+
* 2024 mion
|
|
3
|
+
* Author: Ma-jerez
|
|
4
|
+
* License: MIT
|
|
5
|
+
* The software is provided "as is", without warranty of any kind.
|
|
6
|
+
* ######## */
|
|
7
|
+
|
|
8
|
+
import {TSESTree, TSESLint, AST_NODE_TYPES} from '@typescript-eslint/utils';
|
|
9
|
+
|
|
10
|
+
type PropertyInfo = {name: string; isOptional: boolean};
|
|
11
|
+
type UnionTypeInfo = {node: TSESTree.TypeNode; properties: PropertyInfo[]};
|
|
12
|
+
|
|
13
|
+
function getObjectTypeProperties(node: TSESTree.TypeNode): PropertyInfo[] | null {
|
|
14
|
+
if (node.type === AST_NODE_TYPES.TSTypeLiteral) {
|
|
15
|
+
const props: PropertyInfo[] = [];
|
|
16
|
+
for (const member of node.members) {
|
|
17
|
+
if (member.type === AST_NODE_TYPES.TSPropertySignature && member.key.type === AST_NODE_TYPES.Identifier) {
|
|
18
|
+
props.push({name: member.key.name, isOptional: !!member.optional});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return props;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getUnionObjectTypes(unionNode: TSESTree.TSUnionType): UnionTypeInfo[] {
|
|
27
|
+
const types: UnionTypeInfo[] = [];
|
|
28
|
+
for (const typeNode of unionNode.types) {
|
|
29
|
+
const props = getObjectTypeProperties(typeNode);
|
|
30
|
+
if (props && props.length > 0) {
|
|
31
|
+
types.push({node: typeNode, properties: props});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return types;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getObjectLiteralPropertyNames(node: TSESTree.ObjectExpression): string[] {
|
|
38
|
+
const names: string[] = [];
|
|
39
|
+
for (const prop of node.properties) {
|
|
40
|
+
if (prop.type === AST_NODE_TYPES.Property && prop.key.type === AST_NODE_TYPES.Identifier) {
|
|
41
|
+
names.push(prop.key.name);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return names;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getMatchingUnionTypes(objectProps: string[], unionTypes: UnionTypeInfo[]): number[] {
|
|
48
|
+
const matchingIndices: number[] = [];
|
|
49
|
+
for (let i = 0; i < unionTypes.length; i++) {
|
|
50
|
+
const unionPropNames = new Set(unionTypes[i].properties.map((p) => p.name));
|
|
51
|
+
if (objectProps.some((prop) => unionPropNames.has(prop))) {
|
|
52
|
+
matchingIndices.push(i);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return matchingIndices;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getUniquePropertiesPerType(unionTypes: UnionTypeInfo[]): Map<number, Set<string>> {
|
|
59
|
+
const allProps = new Map<string, number[]>();
|
|
60
|
+
for (let i = 0; i < unionTypes.length; i++) {
|
|
61
|
+
for (const prop of unionTypes[i].properties) {
|
|
62
|
+
const existing = allProps.get(prop.name) || [];
|
|
63
|
+
existing.push(i);
|
|
64
|
+
allProps.set(prop.name, existing);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const uniqueProps = new Map<number, Set<string>>();
|
|
68
|
+
for (let i = 0; i < unionTypes.length; i++) {
|
|
69
|
+
const unique = new Set<string>();
|
|
70
|
+
for (const prop of unionTypes[i].properties) {
|
|
71
|
+
if ((allProps.get(prop.name) || []).length === 1) {
|
|
72
|
+
unique.add(prop.name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
uniqueProps.set(i, unique);
|
|
76
|
+
}
|
|
77
|
+
return uniqueProps;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolves a type reference to its actual union type definition
|
|
82
|
+
*/
|
|
83
|
+
function resolveTypeReference(node: TSESTree.TypeNode, context: TSESLint.RuleContext<any, any>): TSESTree.TSUnionType | null {
|
|
84
|
+
if (node.type !== AST_NODE_TYPES.TSTypeReference) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Get the type name
|
|
89
|
+
if (node.typeName.type !== AST_NODE_TYPES.Identifier) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const typeName = node.typeName.name;
|
|
94
|
+
const sourceCode = context.sourceCode;
|
|
95
|
+
const program = sourceCode.ast;
|
|
96
|
+
|
|
97
|
+
// Find the type alias declaration
|
|
98
|
+
for (const statement of program.body) {
|
|
99
|
+
if (statement.type === AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
100
|
+
if (statement.id.name === typeName && statement.typeAnnotation.type === AST_NODE_TYPES.TSUnionType) {
|
|
101
|
+
return statement.typeAnnotation;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getReturnTypeAnnotation(
|
|
110
|
+
func: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression | TSESTree.FunctionDeclaration,
|
|
111
|
+
context: TSESLint.RuleContext<any, any>
|
|
112
|
+
): TSESTree.TSUnionType | null {
|
|
113
|
+
const returnType = func.returnType?.typeAnnotation;
|
|
114
|
+
if (!returnType) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Direct union type
|
|
119
|
+
if (returnType.type === AST_NODE_TYPES.TSUnionType) {
|
|
120
|
+
return returnType;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Type reference that might resolve to a union type
|
|
124
|
+
if (returnType.type === AST_NODE_TYPES.TSTypeReference) {
|
|
125
|
+
return resolveTypeReference(returnType, context);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function findReturnStatements(
|
|
132
|
+
func: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression | TSESTree.FunctionDeclaration
|
|
133
|
+
): TSESTree.Node[] {
|
|
134
|
+
const returns: TSESTree.Node[] = [];
|
|
135
|
+
if (
|
|
136
|
+
func.type === AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
137
|
+
func.expression &&
|
|
138
|
+
func.body.type !== AST_NODE_TYPES.BlockStatement
|
|
139
|
+
) {
|
|
140
|
+
returns.push(func.body);
|
|
141
|
+
return returns;
|
|
142
|
+
}
|
|
143
|
+
if (func.body.type === AST_NODE_TYPES.BlockStatement) {
|
|
144
|
+
findReturnsInBlock(func.body, returns);
|
|
145
|
+
}
|
|
146
|
+
return returns;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function findReturnsInBlock(block: TSESTree.BlockStatement, returns: TSESTree.Node[]): void {
|
|
150
|
+
for (const statement of block.body) {
|
|
151
|
+
if (statement.type === AST_NODE_TYPES.ReturnStatement && statement.argument) {
|
|
152
|
+
returns.push(statement.argument);
|
|
153
|
+
} else if (statement.type === AST_NODE_TYPES.IfStatement) {
|
|
154
|
+
if (statement.consequent.type === AST_NODE_TYPES.BlockStatement) {
|
|
155
|
+
findReturnsInBlock(statement.consequent, returns);
|
|
156
|
+
} else if (statement.consequent.type === AST_NODE_TYPES.ReturnStatement && statement.consequent.argument) {
|
|
157
|
+
returns.push(statement.consequent.argument);
|
|
158
|
+
}
|
|
159
|
+
if (statement.alternate?.type === AST_NODE_TYPES.BlockStatement) {
|
|
160
|
+
findReturnsInBlock(statement.alternate, returns);
|
|
161
|
+
} else if (statement.alternate?.type === AST_NODE_TYPES.ReturnStatement && statement.alternate.argument) {
|
|
162
|
+
returns.push(statement.alternate.argument);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getConflictDescription(objectProps: string[], unionTypes: UnionTypeInfo[], matchingIndices: number[]): string {
|
|
169
|
+
const uniqueProps = getUniquePropertiesPerType(unionTypes);
|
|
170
|
+
const conflicts: string[] = [];
|
|
171
|
+
for (const idx of matchingIndices) {
|
|
172
|
+
const unique = uniqueProps.get(idx) || new Set();
|
|
173
|
+
const matchingProps = objectProps.filter((p) => unique.has(p));
|
|
174
|
+
if (matchingProps.length > 0) {
|
|
175
|
+
conflicts.push(`'${matchingProps.join("', '")}' from type ${idx + 1}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return conflicts.join(' and ');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function isRouterFunction(node: TSESTree.Node, context: TSESLint.RuleContext<any, any>): boolean {
|
|
182
|
+
let current: TSESTree.Node | undefined = node;
|
|
183
|
+
while (current) {
|
|
184
|
+
if (current.type === AST_NODE_TYPES.CallExpression) {
|
|
185
|
+
return isRouterFunctionCall(current, context);
|
|
186
|
+
}
|
|
187
|
+
current = current.parent;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isRouterFunctionCall(node: TSESTree.CallExpression, context: TSESLint.RuleContext<any, any>): boolean {
|
|
193
|
+
const routerFunctions = ['route', 'middleFn', 'headersFn'];
|
|
194
|
+
if (node.callee.type !== AST_NODE_TYPES.Identifier || !routerFunctions.includes(node.callee.name)) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return isImportedFromMionRouter(node.callee.name, context);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isImportedFromMionRouter(name: string, context: TSESLint.RuleContext<any, any>): boolean {
|
|
201
|
+
const sourceCode = context.sourceCode;
|
|
202
|
+
const program = sourceCode.ast;
|
|
203
|
+
for (const statement of program.body) {
|
|
204
|
+
if (statement.type === AST_NODE_TYPES.ImportDeclaration) {
|
|
205
|
+
const source = statement.source.value;
|
|
206
|
+
if (source === '@mionjs/router' || source === '@mionjs/router/') {
|
|
207
|
+
for (const specifier of statement.specifiers) {
|
|
208
|
+
if (
|
|
209
|
+
specifier.type === AST_NODE_TYPES.ImportSpecifier &&
|
|
210
|
+
specifier.imported.type === AST_NODE_TYPES.Identifier &&
|
|
211
|
+
specifier.imported.name === name
|
|
212
|
+
) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function checkFunction(
|
|
223
|
+
func: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression | TSESTree.FunctionDeclaration,
|
|
224
|
+
context: TSESLint.RuleContext<'mixedUnionProperties', []>
|
|
225
|
+
) {
|
|
226
|
+
if (!isRouterFunction(func, context)) return;
|
|
227
|
+
const unionType = getReturnTypeAnnotation(func, context);
|
|
228
|
+
if (!unionType) return;
|
|
229
|
+
const unionTypes = getUnionObjectTypes(unionType);
|
|
230
|
+
if (unionTypes.length < 2) return;
|
|
231
|
+
const returnStatements = findReturnStatements(func);
|
|
232
|
+
for (const returnNode of returnStatements) {
|
|
233
|
+
if (returnNode.type !== AST_NODE_TYPES.ObjectExpression) continue;
|
|
234
|
+
const objectProps = getObjectLiteralPropertyNames(returnNode);
|
|
235
|
+
if (objectProps.length === 0) continue;
|
|
236
|
+
const matchingIndices = getMatchingUnionTypes(objectProps, unionTypes);
|
|
237
|
+
const uniqueProps = getUniquePropertiesPerType(unionTypes);
|
|
238
|
+
const typesWithUniqueMatches = matchingIndices.filter((idx) => {
|
|
239
|
+
const unique = uniqueProps.get(idx) || new Set();
|
|
240
|
+
return objectProps.some((prop) => unique.has(prop));
|
|
241
|
+
});
|
|
242
|
+
if (typesWithUniqueMatches.length > 1) {
|
|
243
|
+
const conflicts = getConflictDescription(objectProps, unionTypes, typesWithUniqueMatches);
|
|
244
|
+
context.report({node: returnNode, messageId: 'mixedUnionProperties', data: {conflicts}});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const rule: TSESLint.RuleModule<'mixedUnionProperties', []> = {
|
|
250
|
+
meta: {
|
|
251
|
+
type: 'problem',
|
|
252
|
+
docs: {description: 'Detect object literals with properties from multiple union types in router handlers'},
|
|
253
|
+
messages: {
|
|
254
|
+
mixedUnionProperties:
|
|
255
|
+
'Object literal has properties from multiple union types: {{conflicts}}. ' +
|
|
256
|
+
'With loose union matching, only the first matching type will be used for serialization.',
|
|
257
|
+
},
|
|
258
|
+
schema: [],
|
|
259
|
+
},
|
|
260
|
+
defaultOptions: [],
|
|
261
|
+
create(context) {
|
|
262
|
+
return {
|
|
263
|
+
ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression) {
|
|
264
|
+
checkFunction(node, context);
|
|
265
|
+
},
|
|
266
|
+
FunctionExpression(node: TSESTree.FunctionExpression) {
|
|
267
|
+
checkFunction(node, context);
|
|
268
|
+
},
|
|
269
|
+
FunctionDeclaration(node: TSESTree.FunctionDeclaration) {
|
|
270
|
+
checkFunction(node, context);
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export default rule;
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/* ########
|
|
2
|
+
* 2024 mion
|
|
3
|
+
* Author: Ma-jerez
|
|
4
|
+
* License: MIT
|
|
5
|
+
* The software is provided "as is", without warranty of any kind.
|
|
6
|
+
* ######## */
|
|
7
|
+
|
|
8
|
+
import {TSESTree, TSESLint, AST_NODE_TYPES} from '@typescript-eslint/utils';
|
|
9
|
+
|
|
10
|
+
/** List of router functions that should have strongly typed handlers */
|
|
11
|
+
const ROUTER_FUNCTIONS = ['route', 'middleFn', 'headersFn'] as const;
|
|
12
|
+
/** List of handler types that can be used with type annotations */
|
|
13
|
+
const HANDLER_TYPES = ['Handler', 'HeaderHandler'] as const;
|
|
14
|
+
|
|
15
|
+
/** Cache for imports from @mionjs/router - computed once per file */
|
|
16
|
+
interface MionRouterImports {
|
|
17
|
+
/** Set of function names imported from @mionjs/router (route, middleFn, headersFn) */
|
|
18
|
+
routerFunctions: Set<string>;
|
|
19
|
+
/** Set of type names imported from @mionjs/router (Handler, HeaderHandler) */
|
|
20
|
+
handlerTypes: Set<string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Cache for type-only imports - computed once per file */
|
|
24
|
+
interface TypeOnlyImports {
|
|
25
|
+
/** Map of type name to import declaration node */
|
|
26
|
+
typeOnlyImports: Map<string, TSESTree.ImportDeclaration>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Type reference with its node for precise error reporting */
|
|
30
|
+
interface TypeRefWithNode {
|
|
31
|
+
typeName: string;
|
|
32
|
+
node: TSESTree.Node;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Builds a cache of all imports from @mionjs/router */
|
|
36
|
+
function buildRouterImportCache(program: TSESTree.Program): MionRouterImports {
|
|
37
|
+
const routerFunctions = new Set<string>();
|
|
38
|
+
const handlerTypes = new Set<string>();
|
|
39
|
+
|
|
40
|
+
for (const statement of program.body) {
|
|
41
|
+
if (statement.type === AST_NODE_TYPES.ImportDeclaration) {
|
|
42
|
+
const source = statement.source.value;
|
|
43
|
+
if (source === '@mionjs/router' || source === '@mionjs/router/') {
|
|
44
|
+
for (const specifier of statement.specifiers) {
|
|
45
|
+
if (
|
|
46
|
+
specifier.type === AST_NODE_TYPES.ImportSpecifier &&
|
|
47
|
+
specifier.imported.type === AST_NODE_TYPES.Identifier
|
|
48
|
+
) {
|
|
49
|
+
const name = specifier.imported.name;
|
|
50
|
+
if (ROUTER_FUNCTIONS.includes(name as (typeof ROUTER_FUNCTIONS)[number])) {
|
|
51
|
+
routerFunctions.add(name);
|
|
52
|
+
}
|
|
53
|
+
if (HANDLER_TYPES.includes(name as (typeof HANDLER_TYPES)[number])) {
|
|
54
|
+
handlerTypes.add(name);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {routerFunctions, handlerTypes};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Builds a cache of all type-only imports */
|
|
66
|
+
function buildTypeOnlyImportCache(program: TSESTree.Program): TypeOnlyImports {
|
|
67
|
+
const typeOnlyImports = new Map<string, TSESTree.ImportDeclaration>();
|
|
68
|
+
|
|
69
|
+
for (const statement of program.body) {
|
|
70
|
+
if (statement.type === AST_NODE_TYPES.ImportDeclaration) {
|
|
71
|
+
// Check if the entire import is type-only: import type { X } from '...'
|
|
72
|
+
const isTypeOnlyImport = statement.importKind === 'type';
|
|
73
|
+
|
|
74
|
+
for (const specifier of statement.specifiers) {
|
|
75
|
+
if (specifier.type === AST_NODE_TYPES.ImportSpecifier) {
|
|
76
|
+
// Check if individual specifier is type-only: import { type X } from '...'
|
|
77
|
+
const isTypeOnlySpecifier = specifier.importKind === 'type';
|
|
78
|
+
|
|
79
|
+
if (isTypeOnlyImport || isTypeOnlySpecifier) {
|
|
80
|
+
const localName = specifier.local.name;
|
|
81
|
+
typeOnlyImports.set(localName, statement);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {typeOnlyImports};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Checks if a call expression is calling router functions from @mionjs/router */
|
|
92
|
+
function getRouterFunctionName(node: TSESTree.CallExpression, importCache: MionRouterImports): string | null {
|
|
93
|
+
if (node.callee.type !== AST_NODE_TYPES.Identifier) return null;
|
|
94
|
+
const functionName = node.callee.name;
|
|
95
|
+
if (importCache.routerFunctions.has(functionName)) return functionName;
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Extracts all type references with their nodes from a type node recursively */
|
|
100
|
+
function extractTypeReferencesWithNodes(typeNode: TSESTree.TypeNode | null | undefined): TypeRefWithNode[] {
|
|
101
|
+
if (!typeNode) return [];
|
|
102
|
+
|
|
103
|
+
const references: TypeRefWithNode[] = [];
|
|
104
|
+
|
|
105
|
+
switch (typeNode.type) {
|
|
106
|
+
case AST_NODE_TYPES.TSTypeReference:
|
|
107
|
+
if (typeNode.typeName.type === AST_NODE_TYPES.Identifier) {
|
|
108
|
+
// Report on the type reference node itself (e.g., "User" in ": User")
|
|
109
|
+
references.push({typeName: typeNode.typeName.name, node: typeNode});
|
|
110
|
+
}
|
|
111
|
+
// Also check type arguments (generics)
|
|
112
|
+
if (typeNode.typeArguments) {
|
|
113
|
+
for (const param of typeNode.typeArguments.params) {
|
|
114
|
+
references.push(...extractTypeReferencesWithNodes(param));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case AST_NODE_TYPES.TSUnionType:
|
|
120
|
+
case AST_NODE_TYPES.TSIntersectionType:
|
|
121
|
+
for (const member of typeNode.types) {
|
|
122
|
+
references.push(...extractTypeReferencesWithNodes(member));
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case AST_NODE_TYPES.TSArrayType:
|
|
127
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.elementType));
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case AST_NODE_TYPES.TSTupleType:
|
|
131
|
+
for (const element of typeNode.elementTypes) {
|
|
132
|
+
references.push(...extractTypeReferencesWithNodes(element));
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case AST_NODE_TYPES.TSTypeLiteral:
|
|
137
|
+
for (const member of typeNode.members) {
|
|
138
|
+
if (member.type === AST_NODE_TYPES.TSPropertySignature && member.typeAnnotation) {
|
|
139
|
+
references.push(...extractTypeReferencesWithNodes(member.typeAnnotation.typeAnnotation));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
case AST_NODE_TYPES.TSConditionalType:
|
|
145
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.checkType));
|
|
146
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.extendsType));
|
|
147
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.trueType));
|
|
148
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.falseType));
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case AST_NODE_TYPES.TSMappedType:
|
|
152
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.typeAnnotation));
|
|
153
|
+
break;
|
|
154
|
+
|
|
155
|
+
case AST_NODE_TYPES.TSIndexedAccessType:
|
|
156
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.objectType));
|
|
157
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.indexType));
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case AST_NODE_TYPES.TSTypeOperator:
|
|
161
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.typeAnnotation));
|
|
162
|
+
break;
|
|
163
|
+
|
|
164
|
+
case AST_NODE_TYPES.TSRestType:
|
|
165
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.typeAnnotation));
|
|
166
|
+
break;
|
|
167
|
+
|
|
168
|
+
case AST_NODE_TYPES.TSOptionalType:
|
|
169
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.typeAnnotation));
|
|
170
|
+
break;
|
|
171
|
+
|
|
172
|
+
case AST_NODE_TYPES.TSFunctionType:
|
|
173
|
+
case AST_NODE_TYPES.TSConstructorType:
|
|
174
|
+
// Check parameters
|
|
175
|
+
for (const param of typeNode.params) {
|
|
176
|
+
if ('typeAnnotation' in param && param.typeAnnotation) {
|
|
177
|
+
references.push(...extractTypeReferencesWithNodes(param.typeAnnotation.typeAnnotation));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Check return type
|
|
181
|
+
if (typeNode.returnType) {
|
|
182
|
+
references.push(...extractTypeReferencesWithNodes(typeNode.returnType.typeAnnotation));
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return references;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Gets type annotation from a parameter if it exists */
|
|
191
|
+
function getParamTypeAnnotation(param: TSESTree.Parameter): TSESTree.TypeNode | null {
|
|
192
|
+
if (param.type === AST_NODE_TYPES.Identifier && param.typeAnnotation) {
|
|
193
|
+
return param.typeAnnotation.typeAnnotation;
|
|
194
|
+
}
|
|
195
|
+
if (param.type === AST_NODE_TYPES.RestElement && param.typeAnnotation) {
|
|
196
|
+
return param.typeAnnotation.typeAnnotation;
|
|
197
|
+
}
|
|
198
|
+
if (param.type === AST_NODE_TYPES.ArrayPattern && param.typeAnnotation) {
|
|
199
|
+
return param.typeAnnotation.typeAnnotation;
|
|
200
|
+
}
|
|
201
|
+
if (param.type === AST_NODE_TYPES.ObjectPattern && param.typeAnnotation) {
|
|
202
|
+
return param.typeAnnotation.typeAnnotation;
|
|
203
|
+
}
|
|
204
|
+
if (param.type === AST_NODE_TYPES.AssignmentPattern) {
|
|
205
|
+
// Check if there's a type annotation on the pattern itself
|
|
206
|
+
if (param.typeAnnotation) {
|
|
207
|
+
return param.typeAnnotation.typeAnnotation;
|
|
208
|
+
}
|
|
209
|
+
// Check if there's a type annotation on the left side
|
|
210
|
+
if (param.left.type === AST_NODE_TYPES.Identifier && param.left.typeAnnotation) {
|
|
211
|
+
return param.left.typeAnnotation.typeAnnotation;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Extracts type references with nodes from function parameters (excluding first ctx parameter) */
|
|
218
|
+
function extractParamTypeReferencesWithNodes(params: TSESTree.Parameter[]): TypeRefWithNode[] {
|
|
219
|
+
const references: TypeRefWithNode[] = [];
|
|
220
|
+
|
|
221
|
+
// Skip first parameter (ctx)
|
|
222
|
+
for (let i = 1; i < params.length; i++) {
|
|
223
|
+
const param = params[i];
|
|
224
|
+
if (param) {
|
|
225
|
+
const typeAnnotation = getParamTypeAnnotation(param);
|
|
226
|
+
if (typeAnnotation) {
|
|
227
|
+
references.push(...extractTypeReferencesWithNodes(typeAnnotation));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return references;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Extracts type references with nodes from function return type */
|
|
236
|
+
function extractReturnTypeReferencesWithNodes(
|
|
237
|
+
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression | TSESTree.FunctionDeclaration
|
|
238
|
+
): TypeRefWithNode[] {
|
|
239
|
+
if (node.returnType) {
|
|
240
|
+
return extractTypeReferencesWithNodes(node.returnType.typeAnnotation);
|
|
241
|
+
}
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const rule: TSESLint.RuleModule<'noTypeImports', []> = {
|
|
246
|
+
meta: {
|
|
247
|
+
type: 'problem',
|
|
248
|
+
docs: {
|
|
249
|
+
description:
|
|
250
|
+
'Disallow type-only imports for types used in route/middleFn parameters or return types. Type-only imports are erased at runtime, preventing mion from generating validation and serialization functions.',
|
|
251
|
+
},
|
|
252
|
+
messages: {
|
|
253
|
+
noTypeImports:
|
|
254
|
+
'Type "{{typeName}}" is imported as type-only but is used in a route/middleFn. Remove the "type" keyword from the import to allow runtime type reflection.',
|
|
255
|
+
},
|
|
256
|
+
schema: [],
|
|
257
|
+
},
|
|
258
|
+
defaultOptions: [],
|
|
259
|
+
create(context) {
|
|
260
|
+
let routerImportCache: MionRouterImports | null = null;
|
|
261
|
+
let typeOnlyImportCache: TypeOnlyImports | null = null;
|
|
262
|
+
|
|
263
|
+
/** Reports type-only imports used in handler, highlighting the specific type reference */
|
|
264
|
+
function checkTypeReferencesWithNodes(typeRefs: TypeRefWithNode[]): void {
|
|
265
|
+
if (!typeOnlyImportCache) return;
|
|
266
|
+
|
|
267
|
+
// Track reported types to avoid duplicates
|
|
268
|
+
const reportedTypes = new Set<string>();
|
|
269
|
+
|
|
270
|
+
for (const {typeName, node} of typeRefs) {
|
|
271
|
+
// Skip if already reported this type
|
|
272
|
+
if (reportedTypes.has(typeName)) continue;
|
|
273
|
+
|
|
274
|
+
const importDecl = typeOnlyImportCache.typeOnlyImports.get(typeName);
|
|
275
|
+
if (importDecl) {
|
|
276
|
+
reportedTypes.add(typeName);
|
|
277
|
+
context.report({
|
|
278
|
+
node,
|
|
279
|
+
messageId: 'noTypeImports',
|
|
280
|
+
data: {typeName},
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Checks a function node for type-only imports */
|
|
287
|
+
function checkFunctionNode(
|
|
288
|
+
funcNode: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression | TSESTree.FunctionDeclaration
|
|
289
|
+
): void {
|
|
290
|
+
const paramRefs = extractParamTypeReferencesWithNodes(funcNode.params);
|
|
291
|
+
const returnRefs = extractReturnTypeReferencesWithNodes(funcNode);
|
|
292
|
+
const allRefs = [...paramRefs, ...returnRefs];
|
|
293
|
+
|
|
294
|
+
checkTypeReferencesWithNodes(allRefs);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
Program(node) {
|
|
299
|
+
routerImportCache = buildRouterImportCache(node);
|
|
300
|
+
typeOnlyImportCache = buildTypeOnlyImportCache(node);
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
CallExpression(node: TSESTree.CallExpression) {
|
|
304
|
+
if (!routerImportCache || !typeOnlyImportCache) return;
|
|
305
|
+
|
|
306
|
+
const routerFunctionName = getRouterFunctionName(node, routerImportCache);
|
|
307
|
+
if (!routerFunctionName) return;
|
|
308
|
+
|
|
309
|
+
// Check the first argument (handler function)
|
|
310
|
+
const handler = node.arguments[0];
|
|
311
|
+
if (!handler) return;
|
|
312
|
+
|
|
313
|
+
if (
|
|
314
|
+
handler.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
315
|
+
handler.type === AST_NODE_TYPES.FunctionExpression
|
|
316
|
+
) {
|
|
317
|
+
checkFunctionNode(handler);
|
|
318
|
+
} else if (handler.type === AST_NODE_TYPES.Identifier) {
|
|
319
|
+
// Handler is a reference to a function - we need to find the function declaration
|
|
320
|
+
// This is handled by checking function declarations with Handler/HeaderHandler type annotations
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
// Check functions with Handler/HeaderHandler type annotations
|
|
325
|
+
VariableDeclarator(node: TSESTree.VariableDeclarator) {
|
|
326
|
+
if (!routerImportCache || !typeOnlyImportCache) return;
|
|
327
|
+
|
|
328
|
+
// Check if the variable has a Handler or HeaderHandler type annotation
|
|
329
|
+
if (node.id.type === AST_NODE_TYPES.Identifier && node.id.typeAnnotation) {
|
|
330
|
+
const typeAnnotation = node.id.typeAnnotation.typeAnnotation;
|
|
331
|
+
if (
|
|
332
|
+
typeAnnotation.type === AST_NODE_TYPES.TSTypeReference &&
|
|
333
|
+
typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier &&
|
|
334
|
+
routerImportCache.handlerTypes.has(typeAnnotation.typeName.name)
|
|
335
|
+
) {
|
|
336
|
+
// This is a handler with type annotation
|
|
337
|
+
if (
|
|
338
|
+
node.init &&
|
|
339
|
+
(node.init.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
340
|
+
node.init.type === AST_NODE_TYPES.FunctionExpression)
|
|
341
|
+
) {
|
|
342
|
+
checkFunctionNode(node.init);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Check for satisfies expressions
|
|
348
|
+
if (
|
|
349
|
+
node.init &&
|
|
350
|
+
node.init.type === AST_NODE_TYPES.TSSatisfiesExpression &&
|
|
351
|
+
node.init.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference &&
|
|
352
|
+
node.init.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier &&
|
|
353
|
+
routerImportCache.handlerTypes.has(node.init.typeAnnotation.typeName.name)
|
|
354
|
+
) {
|
|
355
|
+
const expr = node.init.expression;
|
|
356
|
+
if (expr.type === AST_NODE_TYPES.ArrowFunctionExpression || expr.type === AST_NODE_TYPES.FunctionExpression) {
|
|
357
|
+
checkFunctionNode(expr);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
// Check function declarations with JSDoc tags
|
|
363
|
+
FunctionDeclaration(node: TSESTree.FunctionDeclaration) {
|
|
364
|
+
if (!routerImportCache || !typeOnlyImportCache) return;
|
|
365
|
+
|
|
366
|
+
// Check for JSDoc tags
|
|
367
|
+
const sourceCode = context.sourceCode;
|
|
368
|
+
const comments = sourceCode.getCommentsBefore(node);
|
|
369
|
+
|
|
370
|
+
for (const comment of comments) {
|
|
371
|
+
if (
|
|
372
|
+
comment.type === 'Block' &&
|
|
373
|
+
(comment.value.includes('@mion:route') ||
|
|
374
|
+
comment.value.includes('@mion:middleFn') ||
|
|
375
|
+
comment.value.includes('@mion:headersFn'))
|
|
376
|
+
) {
|
|
377
|
+
checkFunctionNode(node);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
export default rule;
|