@akanjs/cli 3.0.0-alpha.1 → 3.0.0-alpha.100
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-stamp +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/{abstractCompactor-e5yq60qz.js → abstractCompactor-byytxs22.js} +3 -5
- package/{agent.command-yv00p0s0.js → agent.command-v1a4746v.js} +11 -12
- package/{application.command-0qsczf4k.js → application.command-fzdcnzhg.js} +65 -37
- package/{applicationBuildRunner-ct2pxyvw.js → applicationBuildRunner-s4gsk728.js} +28 -24
- package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-28v9vbzk.js} +8 -9
- package/buildBatch.proc.js +12 -13
- package/capacitorApp-8d6d4zkm.js +12 -0
- package/{cloud.command-dxfv18sa.js → cloud.command-wygy2z05.js} +15 -17
- package/commandManifest.json +1 -1
- package/{context.command-8d9e3k6b.js → context.command-4vdt1aqq.js} +19 -22
- package/dependencyScanner-shs82ynp.js +8 -0
- package/devTui-qnf1txdz.js +553 -0
- package/devTuiView-yzct2cq6.js +9 -0
- package/getDirname-dr3jz1xg.js +7 -0
- package/{guideline.command-3jbkxzh6.js → guideline.command-p92dq6ts.js} +16 -17
- package/guidelines/agentRule/agentRule.generate.json +31 -0
- package/guidelines/agentRule/agentRule.instruction.md +533 -0
- package/guidelines/conventions/conventions.generate.json +25 -0
- package/guidelines/conventions/conventions.instruction.md +908 -0
- package/guidelines/cssRule/cssRule.instruction.md +7 -0
- package/guidelines/framework/framework.generate.json +3 -2
- package/guidelines/framework/framework.instruction.md +1 -9
- package/guidelines/mcpRule/mcpRule.generate.json +25 -0
- package/guidelines/mcpRule/mcpRule.instruction.md +217 -0
- package/guidelines/modelStore/modelStore.instruction.md +12 -0
- package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
- package/guidelines/queryRule/queryRule.generate.json +31 -0
- package/guidelines/queryRule/queryRule.instruction.md +175 -0
- package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
- package/guidelines/runtimeRule/runtimeRule.instruction.md +220 -0
- package/guidelines/ssrRule/ssrRule.instruction.md +49 -5
- package/guidelines/transportRule/transportRule.generate.json +31 -0
- package/guidelines/transportRule/transportRule.instruction.md +60 -0
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +263 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +402 -0
- package/incrementalBuilder.proc.js +26 -24
- package/{index-mr5s7mqd.js → index-0amdxycf.js} +8 -8
- package/{index-t3723f99.js → index-1yxtbmbj.js} +111 -42
- package/{index-ej426mcr.js → index-29sxqfaw.js} +65 -38
- package/index-3d9y066c.js +76 -0
- package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
- package/index-5svjba73.js +159 -0
- package/index-6wwzs1bc.js +7 -0
- package/{index-k95xkh4p.js → index-7d6vny25.js} +16 -12
- package/{index-ec09pws3.js → index-8p8p0cf8.js} +14 -14
- package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
- package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
- package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
- package/{index-0cj2zxbm.js → index-c69j78kk.js} +12 -11
- package/{index-rzwcpcc3.js → index-fc1bh0a7.js} +4 -4
- package/{index-7c6k4yvg.js → index-gq44bqjt.js} +58 -25
- package/{index-a00jg8x8.js → index-gtmz74tj.js} +79 -55
- package/{index-tdbas9r0.js → index-h7yw2z9n.js} +7 -7
- package/{index-bjpxzr6s.js → index-hwkygn9s.js} +51 -3
- package/{index-bbkek1bp.js → index-hyywke0f.js} +1 -1
- package/{index-46tjzh6s.js → index-kcxmv48c.js} +29 -29
- package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
- package/{index-pyt2b4nw.js → index-p5neav9b.js} +1129 -1356
- package/{index-67546d0j.js → index-q0y2jq9j.js} +14 -2
- package/{index-bekddz9q.js → index-qkk27bk6.js} +32 -32
- package/{index-hgna5rjy.js → index-qsq3q8fy.js} +1034 -257
- package/{index-2q1f5j9k.js → index-srdssqj7.js} +18 -18
- package/{index-rh16j3c7.js → index-w8kmx1b6.js} +23 -13
- package/{index-nm6wghb6.js → index-yks023mb.js} +13 -13
- package/{index-1pvex4az.js → index-yza7zkpt.js} +94 -61
- package/{index-yzmhasdb.js → index-zmjppp25.js} +57 -57
- package/{index-8hsbnyes.js → index-znynzt5w.js} +100 -30
- package/{index-zasydjxd.js → index-zxgr3s8s.js} +31 -30
- package/{index-e16gnn26.js → index-zz5jyd94.js} +1002 -128
- package/index.js +22 -24
- package/{library.command-ebdj1vnm.js → library.command-s4fszngs.js} +11 -9
- package/{localRegistry.command-gb178ne8.js → localRegistry.command-gyw4da9z.js} +20 -22
- package/{module.command-9c1bnsx4.js → module.command-6n7dwvav.js} +13 -15
- package/{package.command-t93p4jkj.js → package.command-gr82ypnr.js} +8 -9
- package/package.json +5 -7
- package/{page.command-k3k0meh2.js → page.command-7gvgb3jb.js} +7 -8
- package/{primitive.command-de6nktdt.js → primitive.command-tk5ckw38.js} +13 -15
- package/{quality.command-z5kth2wa.js → quality.command-38v81baf.js} +229 -138
- package/recipeScanner-fpagtgc1.js +11 -0
- package/{repair.command-ehf2g112.js → repair.command-5xbwvsxp.js} +12 -13
- package/{routeSourceValidator-cncd92fv.js → routeSourceValidator-ze6x894c.js} +2 -40
- package/{scalar.command-7a2jtv33.js → scalar.command-0epwcr5k.js} +11 -13
- package/subspace.command-e2t36xb2.js +755 -0
- package/templates/app/env/env.client.debug.ts.template +1 -5
- package/templates/app/env/env.client.develop.ts.template +1 -5
- package/templates/app/env/env.client.local.ts.template +1 -5
- package/templates/app/env/env.client.main.ts.template +1 -5
- package/templates/app/env/env.client.testing.ts.template +1 -5
- package/templates/app/env/env.client.type.ts +2 -6
- package/templates/app/env/env.server.debug.ts.template +1 -5
- package/templates/app/env/env.server.develop.ts.template +1 -5
- package/templates/app/env/env.server.local.ts.template +1 -5
- package/templates/app/env/env.server.main.ts.template +1 -5
- package/templates/app/main.ts +3 -1
- package/templates/app/page/styles.css.template +3 -3
- package/templates/appSample/lib/task/task.signal.ts +11 -6
- package/templates/appSample/srvkit/AuthGuard.ts +9 -5
- package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
- package/templates/crudPages/[__model__Id]/edit/page.tsx +6 -6
- package/templates/crudPages/[__model__Id]/page.tsx +10 -13
- package/templates/crudPages/new/page.tsx +6 -6
- package/templates/crudPages/page.tsx +9 -9
- package/templates/crudSinglePage/page.tsx +11 -7
- package/templates/env/_env.server.type.ts +7 -5
- package/templates/lib/db.ts +4 -2
- package/templates/lib/dict.ts +1 -1
- package/templates/lib/sig.ts +5 -1
- package/templates/lib/srv.ts +6 -2
- package/templates/lib/useClient.ts +9 -3
- package/templates/module/__Model__.Util.tsx +1 -1
- package/templates/module/__Model__.Zone.tsx +3 -5
- package/templates/module/__model__.dictionary.ts +4 -1
- package/templates/module/__model__.signal.ts +2 -3
- package/templates/module/__model__.store.ts +1 -2
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/templates/workspaceRoot/.husky/pre-commit.template +16 -0
- package/templates/workspaceRoot/.vscode/settings.json.template +1 -1
- package/templates/workspaceRoot/AGENTS.md.template +4 -948
- package/templates/workspaceRoot/CLAUDE.md.template +19 -0
- package/templates/workspaceRoot/biome.json.template +4 -198
- package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +15 -16
- package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
- package/templates/workspaceRoot/package.json.template +2 -4
- package/typeChecker-3xgrzc2v.js +7 -0
- package/typecheck.proc.js +4 -5
- package/{workflow.command-e0tt0vk2.js → workflow.command-kphbr7eh.js} +21 -23
- package/{workspace.command-fpda0evj.js → workspace.command-j1gatz3d.js} +108 -91
- package/capacitorApp-4r9sm3qg.js +0 -58
- package/dependencyScanner-f6grrep3.js +0 -9
- package/index-mxvakhsm.js +0 -66
- package/index-r24hmh0q.js +0 -4
- package/recipeScanner-tghkzrrt.js +0 -12
- package/templates/libRoot/base/baseLogic.ts +0 -14
- package/templates/libRoot/base/index.ts +0 -8
- package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
- package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -33
- package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
- package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
- package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
- package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
- package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
- package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
- package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
- package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -18
- package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -61
- package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
- package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
- package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
- package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -58
- package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
- package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
- package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
- package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
- package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
- package/typeChecker-kravn7ns.js +0 -8
- /package/{index-0wae5ebk.js → index-meh558jy.js} +0 -0
- /package/{index-ss469dec.js → index-nz8prx9g.js} +0 -0
|
@@ -1,27 +1,100 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import {
|
|
3
|
-
AbstractDoc
|
|
4
|
-
} from "./index-0wae5ebk.js";
|
|
5
2
|
import {
|
|
6
3
|
Workspace,
|
|
7
|
-
command,
|
|
8
4
|
runner,
|
|
9
|
-
script
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import"./index-
|
|
13
|
-
import
|
|
14
|
-
|
|
5
|
+
script,
|
|
6
|
+
command
|
|
7
|
+
} from "./index-znynzt5w.js";
|
|
8
|
+
import"./index-q0y2jq9j.js";
|
|
9
|
+
import {
|
|
10
|
+
rootAllowedFiles,
|
|
11
|
+
rootAllowedDirs,
|
|
12
|
+
isAllowedLibFacetRootFile
|
|
13
|
+
} from "./index-qsq3q8fy.js";
|
|
14
|
+
import"./index-6wwzs1bc.js";
|
|
15
|
+
import {
|
|
16
|
+
AbstractDoc
|
|
17
|
+
} from "./index-meh558jy.js";
|
|
15
18
|
|
|
16
19
|
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
17
20
|
import { createHash } from "crypto";
|
|
18
21
|
import { readdir, readFile, stat } from "fs/promises";
|
|
19
22
|
import path from "path";
|
|
23
|
+
import { RESERVED_ROUTE_CONFIG_EXPORTS } from "akanjs/common";
|
|
20
24
|
import ignore from "ignore";
|
|
21
|
-
import
|
|
25
|
+
import ts3 from "typescript";
|
|
22
26
|
|
|
23
|
-
// pkgs/@akanjs/devkit/
|
|
27
|
+
// pkgs/@akanjs/devkit/formSetterScanner.ts
|
|
24
28
|
import ts from "typescript";
|
|
29
|
+
|
|
30
|
+
class FormSetterScanner {
|
|
31
|
+
static #fieldSetter = /^set[A-Za-z0-9_$]*On[A-Za-z0-9_$]*$/;
|
|
32
|
+
scan(sourceFiles) {
|
|
33
|
+
return sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".tsx")).flatMap((f) => this.#scanFile(f));
|
|
34
|
+
}
|
|
35
|
+
#scanFile({ file, sourceFile }) {
|
|
36
|
+
const wrapped = [];
|
|
37
|
+
const visit = (node) => {
|
|
38
|
+
if (ts.isJsxAttribute(node)) {
|
|
39
|
+
const setter = FormSetterScanner.#wrappedSetterOf(node);
|
|
40
|
+
if (setter)
|
|
41
|
+
wrapped.push({ setter, line: FormSetterScanner.#lineOf(sourceFile, node) });
|
|
42
|
+
}
|
|
43
|
+
ts.forEachChild(node, visit);
|
|
44
|
+
};
|
|
45
|
+
visit(sourceFile);
|
|
46
|
+
if (!wrapped.length)
|
|
47
|
+
return [];
|
|
48
|
+
const setters = [...new Set(wrapped.map((entry) => entry.setter))].sort();
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
rule: "akan.agent.unpublished-form-setter",
|
|
52
|
+
scope: "agent",
|
|
53
|
+
severity: "warning",
|
|
54
|
+
message: setters.length === 1 ? `${setters[0]} is reached through a wrapper, so that field publishes no agent tool and carries no data-akan-action.` : `${setters.length} field setters are reached through a wrapper, so those fields publish no agent tool and carry no data-akan-action: ${setters.join(", ")}.`,
|
|
55
|
+
file,
|
|
56
|
+
line: wrapped[0]?.line,
|
|
57
|
+
locations: wrapped.map(({ line }) => ({ file, line }))
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
static #wrappedSetterOf(attribute) {
|
|
62
|
+
if (!ts.isIdentifier(attribute.name) || !/^on[A-Z]/.test(attribute.name.text))
|
|
63
|
+
return null;
|
|
64
|
+
const initializer = attribute.initializer;
|
|
65
|
+
if (!initializer || !ts.isJsxExpression(initializer) || !initializer.expression)
|
|
66
|
+
return null;
|
|
67
|
+
const handler = initializer.expression;
|
|
68
|
+
if (!ts.isArrowFunction(handler) && !ts.isFunctionExpression(handler))
|
|
69
|
+
return null;
|
|
70
|
+
if (!handler.parameters.length)
|
|
71
|
+
return null;
|
|
72
|
+
return FormSetterScanner.#setterCallIn(handler.body);
|
|
73
|
+
}
|
|
74
|
+
static #setterCallIn(node) {
|
|
75
|
+
let found = null;
|
|
76
|
+
const visit = (current) => {
|
|
77
|
+
if (found)
|
|
78
|
+
return;
|
|
79
|
+
if (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
80
|
+
const { expression, name } = current.expression;
|
|
81
|
+
if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "st" && expression.name.text === "do" && FormSetterScanner.#fieldSetter.test(name.text)) {
|
|
82
|
+
found = name.text;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
ts.forEachChild(current, visit);
|
|
87
|
+
};
|
|
88
|
+
visit(node);
|
|
89
|
+
return found;
|
|
90
|
+
}
|
|
91
|
+
static #lineOf(sourceFile, node) {
|
|
92
|
+
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// pkgs/@akanjs/devkit/ssrScanner.ts
|
|
97
|
+
import ts2 from "typescript";
|
|
25
98
|
var STATIC_COMPONENT_MIN_MASS = 4;
|
|
26
99
|
var MIXED_COMPONENT_MIN_MASS = 10;
|
|
27
100
|
var MIXED_COMPONENT_MAX_TOUCHES = 2;
|
|
@@ -139,31 +212,31 @@ class SsrScanner {
|
|
|
139
212
|
});
|
|
140
213
|
}
|
|
141
214
|
}
|
|
142
|
-
|
|
215
|
+
ts2.forEachChild(node, visit);
|
|
143
216
|
};
|
|
144
|
-
|
|
217
|
+
ts2.forEachChild(sourceFile.sourceFile, visit);
|
|
145
218
|
return warnings;
|
|
146
219
|
}
|
|
147
220
|
#isMountEffect(sourceFile, node) {
|
|
148
|
-
if (!
|
|
221
|
+
if (!ts2.isCallExpression(node))
|
|
149
222
|
return false;
|
|
150
223
|
const callee = node.expression.getText(sourceFile);
|
|
151
224
|
if (callee !== "useEffect" && callee !== "useLayoutEffect")
|
|
152
225
|
return false;
|
|
153
226
|
const deps = node.arguments[1];
|
|
154
|
-
return !!deps &&
|
|
227
|
+
return !!deps && ts2.isArrayLiteralExpression(deps) && deps.elements.length === 0;
|
|
155
228
|
}
|
|
156
229
|
#getLoadCalls(sourceFile, node) {
|
|
157
230
|
const calls = [];
|
|
158
231
|
const visit = (child) => {
|
|
159
|
-
if (
|
|
232
|
+
if (ts2.isCallExpression(child)) {
|
|
160
233
|
const callee = child.expression.getText(sourceFile);
|
|
161
234
|
if (/^fetch\.[a-z]/.test(callee) || /^st\.do\.(init|get|view|load|list|count|insight)[A-Z]/.test(callee))
|
|
162
235
|
calls.push({ callee, node: child });
|
|
163
236
|
}
|
|
164
|
-
|
|
237
|
+
ts2.forEachChild(child, visit);
|
|
165
238
|
};
|
|
166
|
-
|
|
239
|
+
ts2.forEachChild(node, visit);
|
|
167
240
|
return calls;
|
|
168
241
|
}
|
|
169
242
|
#getTemplateStateWarnings(sourceFile) {
|
|
@@ -171,7 +244,7 @@ class SsrScanner {
|
|
|
171
244
|
return [];
|
|
172
245
|
const warnings = [];
|
|
173
246
|
const visit = (node) => {
|
|
174
|
-
if (
|
|
247
|
+
if (ts2.isCallExpression(node) && node.expression.getText(sourceFile.sourceFile) === "useState") {
|
|
175
248
|
warnings.push({
|
|
176
249
|
rule: "akan.ssr.template-client-state",
|
|
177
250
|
scope: "ssr",
|
|
@@ -181,9 +254,9 @@ class SsrScanner {
|
|
|
181
254
|
message: "Template holds form state in useState. Templates are store-driven and carry no local state."
|
|
182
255
|
});
|
|
183
256
|
}
|
|
184
|
-
|
|
257
|
+
ts2.forEachChild(node, visit);
|
|
185
258
|
};
|
|
186
|
-
|
|
259
|
+
ts2.forEachChild(sourceFile.sourceFile, visit);
|
|
187
260
|
return warnings;
|
|
188
261
|
}
|
|
189
262
|
#measureBalance(sourceFiles) {
|
|
@@ -224,14 +297,14 @@ class SsrScanner {
|
|
|
224
297
|
return components;
|
|
225
298
|
}
|
|
226
299
|
#getComponentNodes(statement) {
|
|
227
|
-
if (
|
|
300
|
+
if (ts2.isFunctionDeclaration(statement) && statement.body)
|
|
228
301
|
return [{ name: statement.name?.text ?? "default", node: statement.body }];
|
|
229
|
-
if (!
|
|
302
|
+
if (!ts2.isVariableStatement(statement))
|
|
230
303
|
return [];
|
|
231
304
|
return statement.declarationList.declarations.flatMap((declaration) => {
|
|
232
|
-
if (!
|
|
305
|
+
if (!ts2.isIdentifier(declaration.name) || !declaration.initializer)
|
|
233
306
|
return [];
|
|
234
|
-
if (!
|
|
307
|
+
if (!ts2.isArrowFunction(declaration.initializer) && !ts2.isFunctionExpression(declaration.initializer))
|
|
235
308
|
return [];
|
|
236
309
|
return [{ name: declaration.name.text, node: declaration.initializer }];
|
|
237
310
|
});
|
|
@@ -239,7 +312,7 @@ class SsrScanner {
|
|
|
239
312
|
#getTouches(sourceFile, node) {
|
|
240
313
|
const touches = [];
|
|
241
314
|
const visit = (child) => {
|
|
242
|
-
if (
|
|
315
|
+
if (ts2.isCallExpression(child)) {
|
|
243
316
|
const callee = child.expression.getText(sourceFile);
|
|
244
317
|
const bareName = callee.split(".").pop() ?? callee;
|
|
245
318
|
if (callee === "createContext" || callee === "lazy")
|
|
@@ -247,60 +320,60 @@ class SsrScanner {
|
|
|
247
320
|
else if (/^use[A-Z]/.test(bareName) && !SsrScanner.#serverSafeCalls.has(bareName))
|
|
248
321
|
touches.push(bareName);
|
|
249
322
|
}
|
|
250
|
-
if (
|
|
323
|
+
if (ts2.isJsxAttribute(child) && /^on[A-Z]/.test(child.name.getText(sourceFile)))
|
|
251
324
|
touches.push(child.name.getText(sourceFile));
|
|
252
|
-
if (
|
|
325
|
+
if (ts2.isPropertyAccessExpression(child)) {
|
|
253
326
|
const root = getAccessRoot(child);
|
|
254
327
|
if (root === "st")
|
|
255
328
|
touches.push("st");
|
|
256
329
|
else if (SsrScanner.#clientGlobals.has(root))
|
|
257
330
|
touches.push(root);
|
|
258
331
|
}
|
|
259
|
-
|
|
332
|
+
ts2.forEachChild(child, visit);
|
|
260
333
|
};
|
|
261
|
-
|
|
334
|
+
ts2.forEachChild(node, visit);
|
|
262
335
|
return touches;
|
|
263
336
|
}
|
|
264
337
|
#getTagNames(sourceFile, node) {
|
|
265
338
|
const tags = new Set;
|
|
266
339
|
const visit = (child) => {
|
|
267
|
-
if (
|
|
340
|
+
if (ts2.isJsxOpeningElement(child) || ts2.isJsxSelfClosingElement(child))
|
|
268
341
|
tags.add(child.tagName.getText(sourceFile).split(".")[0]);
|
|
269
|
-
|
|
342
|
+
ts2.forEachChild(child, visit);
|
|
270
343
|
};
|
|
271
|
-
|
|
344
|
+
ts2.forEachChild(node, visit);
|
|
272
345
|
return tags;
|
|
273
346
|
}
|
|
274
347
|
#getMass(node) {
|
|
275
348
|
let mass = 0;
|
|
276
349
|
const visit = (child) => {
|
|
277
|
-
if (
|
|
350
|
+
if (ts2.isJsxOpeningElement(child) || ts2.isJsxSelfClosingElement(child))
|
|
278
351
|
mass += 1;
|
|
279
|
-
|
|
352
|
+
ts2.forEachChild(child, visit);
|
|
280
353
|
};
|
|
281
|
-
|
|
354
|
+
ts2.forEachChild(node, visit);
|
|
282
355
|
return mass;
|
|
283
356
|
}
|
|
284
357
|
#hasUseClient(sourceFile) {
|
|
285
358
|
const first = sourceFile.statements[0];
|
|
286
|
-
if (!first || !
|
|
359
|
+
if (!first || !ts2.isExpressionStatement(first) || !ts2.isStringLiteral(first.expression))
|
|
287
360
|
return false;
|
|
288
361
|
return first.expression.text === "use client";
|
|
289
362
|
}
|
|
290
363
|
#hasVendorImport(sourceFile) {
|
|
291
|
-
return sourceFile.statements.some((statement) =>
|
|
364
|
+
return sourceFile.statements.some((statement) => ts2.isImportDeclaration(statement) && isVendorSpecifier(getSpecifier(statement)));
|
|
292
365
|
}
|
|
293
366
|
#getVendorNames(sourceFile) {
|
|
294
367
|
const names = new Set;
|
|
295
368
|
for (const statement of sourceFile.statements) {
|
|
296
|
-
if (!
|
|
369
|
+
if (!ts2.isImportDeclaration(statement) || !isVendorSpecifier(getSpecifier(statement)))
|
|
297
370
|
continue;
|
|
298
371
|
const clause = statement.importClause;
|
|
299
372
|
if (clause?.name)
|
|
300
373
|
names.add(clause.name.text);
|
|
301
|
-
if (clause?.namedBindings &&
|
|
374
|
+
if (clause?.namedBindings && ts2.isNamespaceImport(clause.namedBindings))
|
|
302
375
|
names.add(clause.namedBindings.name.text);
|
|
303
|
-
if (clause?.namedBindings &&
|
|
376
|
+
if (clause?.namedBindings && ts2.isNamedImports(clause.namedBindings))
|
|
304
377
|
for (const element of clause.namedBindings.elements)
|
|
305
378
|
names.add(element.name.text);
|
|
306
379
|
}
|
|
@@ -308,10 +381,10 @@ class SsrScanner {
|
|
|
308
381
|
}
|
|
309
382
|
#importsClientRuntime(sourceFile) {
|
|
310
383
|
for (const statement of sourceFile.statements) {
|
|
311
|
-
if (!
|
|
384
|
+
if (!ts2.isImportDeclaration(statement))
|
|
312
385
|
continue;
|
|
313
386
|
const bindings = statement.importClause?.namedBindings;
|
|
314
|
-
if (!bindings || !
|
|
387
|
+
if (!bindings || !ts2.isNamedImports(bindings))
|
|
315
388
|
continue;
|
|
316
389
|
if (bindings.elements.some((element) => SsrScanner.#clientRuntimeImports.has(element.name.text)))
|
|
317
390
|
return true;
|
|
@@ -362,7 +435,7 @@ function getShare(serverMass, clientMass) {
|
|
|
362
435
|
return total === 0 ? 1 : serverMass / total;
|
|
363
436
|
}
|
|
364
437
|
function getSpecifier(statement) {
|
|
365
|
-
return
|
|
438
|
+
return ts2.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
|
|
366
439
|
}
|
|
367
440
|
function isVendorSpecifier(specifier) {
|
|
368
441
|
if (specifier === "" || specifier.startsWith(".") || specifier.startsWith("/"))
|
|
@@ -375,9 +448,9 @@ function isVendorSpecifier(specifier) {
|
|
|
375
448
|
}
|
|
376
449
|
function getAccessRoot(node) {
|
|
377
450
|
let current = node;
|
|
378
|
-
while (
|
|
451
|
+
while (ts2.isPropertyAccessExpression(current))
|
|
379
452
|
current = current.expression;
|
|
380
|
-
return
|
|
453
|
+
return ts2.isIdentifier(current) ? current.text : "";
|
|
381
454
|
}
|
|
382
455
|
|
|
383
456
|
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
@@ -401,30 +474,10 @@ var SUGGESTED_RULES = [
|
|
|
401
474
|
"Keep apps/*/lib and libs/*/lib root files limited to generated support facets such as cnst.ts, db.ts, dict.ts, sig.ts, srv.ts, st.ts, useClient.ts, and useServer.ts.",
|
|
402
475
|
"Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
|
|
403
476
|
"Keep module UI filenames predictable: database modules use <Model>.Template/Unit/Util/View/Zone.tsx, service modules use <Service>.Util/Zone.tsx, and scalar modules use <Scalar>.Template/Unit.tsx.",
|
|
404
|
-
"
|
|
477
|
+
"Hand a form control its store setter by reference so the field publishes an agent tool; put normalization in the control's transform prop and a multi-write in a _postSet<Field> hook on the store.",
|
|
478
|
+
"Move shared app and lib utilities to common/ instead of creating apps/*/base or libs/*/base.",
|
|
405
479
|
"Avoid large mixed-purpose class files; class export files should import helpers from neighboring utility files instead of declaring them inline."
|
|
406
480
|
];
|
|
407
|
-
var APP_ROOT_FILES = new Set([
|
|
408
|
-
"akan.app.json",
|
|
409
|
-
"akan.config.ts",
|
|
410
|
-
"capacitor.config.ts",
|
|
411
|
-
"client.ts",
|
|
412
|
-
"main.ts",
|
|
413
|
-
"package.json",
|
|
414
|
-
"server.ts",
|
|
415
|
-
"tsconfig.json"
|
|
416
|
-
]);
|
|
417
|
-
var LIB_ROOT_FILES = new Set([
|
|
418
|
-
"cnst.ts",
|
|
419
|
-
"db.ts",
|
|
420
|
-
"dict.ts",
|
|
421
|
-
"option.ts",
|
|
422
|
-
"sig.ts",
|
|
423
|
-
"srv.ts",
|
|
424
|
-
"st.ts",
|
|
425
|
-
"useClient.ts",
|
|
426
|
-
"useServer.ts"
|
|
427
|
-
]);
|
|
428
481
|
var CONVENTION_SUFFIXES = [
|
|
429
482
|
".constant.ts",
|
|
430
483
|
".dictionary.ts",
|
|
@@ -433,20 +486,6 @@ var CONVENTION_SUFFIXES = [
|
|
|
433
486
|
".signal.ts",
|
|
434
487
|
".store.ts"
|
|
435
488
|
];
|
|
436
|
-
var PAGE_RESERVED_EXPORTS = new Set([
|
|
437
|
-
"pageConfig",
|
|
438
|
-
"head",
|
|
439
|
-
"metadata",
|
|
440
|
-
"generateHead",
|
|
441
|
-
"generateMetadata",
|
|
442
|
-
"fonts",
|
|
443
|
-
"manifest",
|
|
444
|
-
"theme",
|
|
445
|
-
"reconnect",
|
|
446
|
-
"wsConnect",
|
|
447
|
-
"layoutStyle",
|
|
448
|
-
"gaTrackingId"
|
|
449
|
-
]);
|
|
450
489
|
var RULE_FIXES = {
|
|
451
490
|
"akan.global.duplicate-exported-function-name": "Rename one of the exports, or if they are the same thing, extract it into one shared module and import it in both places.",
|
|
452
491
|
"akan.global.duplicate-exported-function-body": "Extract the shared implementation into a single exported helper and import it, instead of copying the body.",
|
|
@@ -454,15 +493,20 @@ var RULE_FIXES = {
|
|
|
454
493
|
"akan.file.max-lines": "Break the file into smaller focused modules; keep one primary responsibility per file.",
|
|
455
494
|
"akan.file.abstract-max-lines": "Run `akan compact <app-or-lib>` to rewrite the abstract with the AI editor, keeping only the invariants and workflows the source files cannot show.",
|
|
456
495
|
"akan.file.placeholder-export": "Remove the placeholder export; generated indexes should only re-export real modules.",
|
|
496
|
+
"akan.file.bang-comment-in-client": "Rewrite the marker as `// FIXME:` or `// TODO:`. Keep the bang form for server, srvkit, and CLI files.",
|
|
457
497
|
"akan.file.dictionary-stale-text": "Replace the scaffold text with real localized copy for this dictionary entry.",
|
|
458
498
|
"akan.file.global-declaration": "Move the global declaration into an approved low-level integration file (e.g. webkit) and keep it isolated.",
|
|
459
499
|
"akan.file.window-augmentation": "Move the Window augmentation into an approved browser integration file and keep it isolated.",
|
|
460
500
|
"akan.file.prototype-mutation": "Avoid prototype mutation, or isolate it in an approved low-level integration file.",
|
|
461
501
|
"akan.file.class-export-global-declaration": "Move the helper to a sibling file and import it into the class module.",
|
|
502
|
+
"akan.agent.unpublished-form-setter": "Pass the setter by reference where the wrapper only forwards (onChange={st.do.setTitleOnTask}); move normalization into the control's own transform prop; move a multi-write into a _postSet<Field> hook on the store, keeping the generated setter on the control.",
|
|
462
503
|
"akan.file.component-internal-declaration": "Move the type or helper to a type/util file in ui/, webkit/, or common/ by purpose. If it is the component's props, declare it as `interface <Component>Props`.",
|
|
463
504
|
"akan.file.component-export": "Move the value or type to a util/constant/type file in ui/, webkit/, or common/ and import it. Adding `export` is not a valid fix \u2014 only PascalCase components and their `<Component>Props` interface belong here.",
|
|
464
505
|
"akan.layout.app-root-file": "Move the file into a conventional app folder (common, env, lib, page, private, public, script, srvkit, ui, or webkit).",
|
|
465
|
-
"akan.layout.
|
|
506
|
+
"akan.layout.app-root-folder": "Move the folder's contents into a conventional app folder (common, env, lib, page, private, public, script, srvkit, ui, or webkit) and delete it.",
|
|
507
|
+
"akan.layout.lib-root-file": "Move the file into a conventional lib folder (common, env, lib, page, private, public, srvkit, ui, or webkit).",
|
|
508
|
+
"akan.layout.lib-root-folder": "Move the folder's contents into a conventional lib folder (common, env, lib, page, private, public, srvkit, ui, or webkit) and delete it.",
|
|
509
|
+
"akan.layout.lib-facet-file": "Move the file into a domain module folder under lib/; keep the lib facet root limited to generated support facets.",
|
|
466
510
|
"akan.layout.module-ui-file": "Rename the file to an allowed module UI name, or move it to ui/ if it is not a module component.",
|
|
467
511
|
"akan.ssr.unnecessary-use-client": 'Delete the "use client" directive so the file renders on the server. If it exists only to wrap one client child, drop the wrapper and use the child directly.',
|
|
468
512
|
"akan.ssr.client-static-component": "Move the component to a server file \u2014 a <Model>.Unit.tsx / <Model>.View.tsx for a module, or a ui/ file with no directive \u2014 and reference it from the client file.",
|
|
@@ -490,6 +534,7 @@ class AkanQualityScanner {
|
|
|
490
534
|
...sourceFiles.flatMap((sourceFile) => this.#scanConventionQuality(sourceFile)),
|
|
491
535
|
...sourceFiles.flatMap((sourceFile) => this.#scanLayoutQuality(sourceFile)),
|
|
492
536
|
...abstractFiles.flatMap((abstractFile) => this.#scanAbstractQuality(abstractFile)),
|
|
537
|
+
...new FormSetterScanner().scan(sourceFiles),
|
|
493
538
|
...ssr.warnings
|
|
494
539
|
];
|
|
495
540
|
return {
|
|
@@ -542,7 +587,7 @@ class AkanQualityScanner {
|
|
|
542
587
|
file,
|
|
543
588
|
absolutePath,
|
|
544
589
|
content,
|
|
545
|
-
sourceFile:
|
|
590
|
+
sourceFile: ts3.createSourceFile(file, content, ts3.ScriptTarget.Latest, true, getScriptKind(file))
|
|
546
591
|
};
|
|
547
592
|
}
|
|
548
593
|
async#readTextFile(workspaceRoot, file) {
|
|
@@ -600,6 +645,7 @@ class AkanQualityScanner {
|
|
|
600
645
|
message: `File has ${lineCount} lines. Keep single files under ${MAX_FILE_LINES} lines.`
|
|
601
646
|
});
|
|
602
647
|
}
|
|
648
|
+
warnings.push(...getBangCommentWarnings(sourceFile));
|
|
603
649
|
warnings.push(...getPlaceholderExportWarnings(sourceFile));
|
|
604
650
|
warnings.push(...getDictionaryTextWarnings(sourceFile));
|
|
605
651
|
warnings.push(...getGlobalMutationWarnings(sourceFile));
|
|
@@ -701,25 +747,30 @@ class AkanQualityScanner {
|
|
|
701
747
|
];
|
|
702
748
|
}
|
|
703
749
|
#scanLayoutQuality(sourceFile) {
|
|
704
|
-
const segments = sourceFile.file.split("/");
|
|
705
750
|
const warnings = [];
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
751
|
+
const rootEntry = getSysRootEntry(sourceFile.file);
|
|
752
|
+
if (rootEntry) {
|
|
753
|
+
const { type, name, isDir } = rootEntry;
|
|
754
|
+
const allowed = isDir ? rootAllowedDirs[type].has(name) : rootAllowedFiles[type].has(name);
|
|
755
|
+
const kind = isDir ? "folder" : "file";
|
|
756
|
+
if (!allowed) {
|
|
757
|
+
warnings.push({
|
|
758
|
+
rule: `akan.layout.${type}-root-${kind}`,
|
|
759
|
+
scope: "layout",
|
|
760
|
+
severity: "warning",
|
|
761
|
+
file: sourceFile.file,
|
|
762
|
+
message: `Unexpected ${type} root ${kind} "${name}". Keep ${type} code in conventional ${type} folders.`
|
|
763
|
+
});
|
|
764
|
+
}
|
|
714
765
|
}
|
|
715
|
-
const
|
|
716
|
-
if (
|
|
766
|
+
const libFacetFile = getLibFacetRootFile(sourceFile.file);
|
|
767
|
+
if (libFacetFile && !isAllowedLibFacetRootFile(libFacetFile)) {
|
|
717
768
|
warnings.push({
|
|
718
|
-
rule: "akan.layout.lib-
|
|
769
|
+
rule: "akan.layout.lib-facet-file",
|
|
719
770
|
scope: "layout",
|
|
720
771
|
severity: "warning",
|
|
721
772
|
file: sourceFile.file,
|
|
722
|
-
message: `Unexpected lib root file "${
|
|
773
|
+
message: `Unexpected lib facet root file "${libFacetFile}". Keep direct lib/ root files limited to generated support facets.`
|
|
723
774
|
});
|
|
724
775
|
}
|
|
725
776
|
const moduleUiWarning = getModuleUiWarning(sourceFile.file);
|
|
@@ -789,7 +840,7 @@ function getExportedFunctionLikes(sourceFile) {
|
|
|
789
840
|
const declarations = [];
|
|
790
841
|
const nameExempt = isPageRouteFile(sourceFile.file) || isUiComponentFile(sourceFile.file);
|
|
791
842
|
for (const statement of sourceFile.sourceFile.statements) {
|
|
792
|
-
if (
|
|
843
|
+
if (ts3.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
|
|
793
844
|
declarations.push({
|
|
794
845
|
name: statement.name.text,
|
|
795
846
|
kind: "function",
|
|
@@ -799,7 +850,7 @@ function getExportedFunctionLikes(sourceFile) {
|
|
|
799
850
|
duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, false)
|
|
800
851
|
});
|
|
801
852
|
}
|
|
802
|
-
if (
|
|
853
|
+
if (ts3.isClassDeclaration(statement) && statement.name && isExported(statement)) {
|
|
803
854
|
declarations.push({
|
|
804
855
|
name: statement.name.text,
|
|
805
856
|
kind: "class",
|
|
@@ -809,9 +860,9 @@ function getExportedFunctionLikes(sourceFile) {
|
|
|
809
860
|
duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, isEnumClassStatement(sourceFile.sourceFile, statement))
|
|
810
861
|
});
|
|
811
862
|
}
|
|
812
|
-
if (
|
|
863
|
+
if (ts3.isVariableStatement(statement) && isExported(statement)) {
|
|
813
864
|
for (const declaration of statement.declarationList.declarations) {
|
|
814
|
-
if (!
|
|
865
|
+
if (!ts3.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
|
|
815
866
|
continue;
|
|
816
867
|
declarations.push({
|
|
817
868
|
name: declaration.name.text,
|
|
@@ -850,14 +901,14 @@ function isInLibModule(file) {
|
|
|
850
901
|
return (segments[0] === "apps" || segments[0] === "libs") && segments.includes("lib");
|
|
851
902
|
}
|
|
852
903
|
function isEnumClassStatement(sourceFile, statement) {
|
|
853
|
-
if (!
|
|
904
|
+
if (!ts3.isClassDeclaration(statement))
|
|
854
905
|
return false;
|
|
855
|
-
const heritageClause = statement.heritageClauses?.find((clause) => clause.token ===
|
|
906
|
+
const heritageClause = statement.heritageClauses?.find((clause) => clause.token === ts3.SyntaxKind.ExtendsKeyword);
|
|
856
907
|
const expression = heritageClause?.types[0]?.expression;
|
|
857
908
|
return !!expression && expression.getText(sourceFile).startsWith("enumOf(");
|
|
858
909
|
}
|
|
859
910
|
function getExportedClassNames(sourceFile) {
|
|
860
|
-
return sourceFile.statements.filter((statement) =>
|
|
911
|
+
return sourceFile.statements.filter((statement) => ts3.isClassDeclaration(statement)).filter((statement) => isExported(statement)).map((statement) => statement.name?.text).filter((name) => !!name);
|
|
861
912
|
}
|
|
862
913
|
function isComponentDeclarationFile(file) {
|
|
863
914
|
if (!file.endsWith(".tsx"))
|
|
@@ -873,7 +924,7 @@ function isComponentDeclarationFile(file) {
|
|
|
873
924
|
function getComponentFileDeclarations(sourceFile) {
|
|
874
925
|
const reExportedNames = new Set;
|
|
875
926
|
for (const statement of sourceFile.statements) {
|
|
876
|
-
if (
|
|
927
|
+
if (ts3.isExportDeclaration(statement) && statement.exportClause && ts3.isNamedExports(statement.exportClause)) {
|
|
877
928
|
for (const element of statement.exportClause.elements)
|
|
878
929
|
reExportedNames.add((element.propertyName ?? element.name).text);
|
|
879
930
|
}
|
|
@@ -883,19 +934,19 @@ function getComponentFileDeclarations(sourceFile) {
|
|
|
883
934
|
const isDefaultExport = isDefaultExportStatement(statement);
|
|
884
935
|
const inlineExported = isExported(statement);
|
|
885
936
|
const add = (name, kind, line) => declarations.push({ name, kind, line, exported: inlineExported || reExportedNames.has(name), isDefaultExport });
|
|
886
|
-
if (
|
|
937
|
+
if (ts3.isInterfaceDeclaration(statement))
|
|
887
938
|
add(statement.name.text, "interface", getLine(sourceFile, statement));
|
|
888
|
-
else if (
|
|
939
|
+
else if (ts3.isTypeAliasDeclaration(statement))
|
|
889
940
|
add(statement.name.text, "type", getLine(sourceFile, statement));
|
|
890
|
-
else if (
|
|
941
|
+
else if (ts3.isEnumDeclaration(statement))
|
|
891
942
|
add(statement.name.text, "enum", getLine(sourceFile, statement));
|
|
892
|
-
else if (
|
|
943
|
+
else if (ts3.isFunctionDeclaration(statement) && statement.name)
|
|
893
944
|
add(statement.name.text, "function", getLine(sourceFile, statement));
|
|
894
|
-
else if (
|
|
945
|
+
else if (ts3.isClassDeclaration(statement) && statement.name)
|
|
895
946
|
add(statement.name.text, "class", getLine(sourceFile, statement));
|
|
896
|
-
else if (
|
|
947
|
+
else if (ts3.isVariableStatement(statement)) {
|
|
897
948
|
for (const declaration of statement.declarationList.declarations) {
|
|
898
|
-
if (!
|
|
949
|
+
if (!ts3.isIdentifier(declaration.name))
|
|
899
950
|
continue;
|
|
900
951
|
const kind = isFunctionLikeInitializer(declaration.initializer) ? "function" : "variable";
|
|
901
952
|
add(declaration.name.text, kind, getLine(sourceFile, declaration));
|
|
@@ -907,15 +958,15 @@ function getComponentFileDeclarations(sourceFile) {
|
|
|
907
958
|
function getCompoundComponentNames(sourceFile) {
|
|
908
959
|
const names = new Set;
|
|
909
960
|
for (const statement of sourceFile.statements) {
|
|
910
|
-
if (!
|
|
961
|
+
if (!ts3.isExpressionStatement(statement))
|
|
911
962
|
continue;
|
|
912
963
|
const { expression } = statement;
|
|
913
|
-
if (!
|
|
964
|
+
if (!ts3.isBinaryExpression(expression) || expression.operatorToken.kind !== ts3.SyntaxKind.EqualsToken)
|
|
914
965
|
continue;
|
|
915
|
-
if (!
|
|
966
|
+
if (!ts3.isPropertyAccessExpression(expression.left) || !isPascalCaseName(expression.left.name.text))
|
|
916
967
|
continue;
|
|
917
968
|
names.add(expression.left.name.text);
|
|
918
|
-
if (
|
|
969
|
+
if (ts3.isIdentifier(expression.right) && isPascalCaseName(expression.right.text))
|
|
919
970
|
names.add(expression.right.text);
|
|
920
971
|
}
|
|
921
972
|
return names;
|
|
@@ -929,15 +980,43 @@ function isRestrictedInternalKind(kind) {
|
|
|
929
980
|
function isAllowedComponentExport(declaration, isPage) {
|
|
930
981
|
if (isComponentValueKind(declaration.kind) && isPascalCaseName(declaration.name))
|
|
931
982
|
return true;
|
|
932
|
-
return isPage &&
|
|
983
|
+
return isPage && RESERVED_ROUTE_CONFIG_EXPORTS.has(declaration.name);
|
|
933
984
|
}
|
|
934
985
|
function isPascalCaseName(name) {
|
|
935
986
|
return /^[A-Z]/.test(name) && !/^[A-Z0-9_]+$/.test(name);
|
|
936
987
|
}
|
|
937
988
|
function isDefaultExportStatement(statement) {
|
|
938
|
-
if (
|
|
989
|
+
if (ts3.isExportAssignment(statement))
|
|
939
990
|
return !statement.isExportEquals;
|
|
940
|
-
|
|
991
|
+
if (!ts3.canHaveModifiers(statement))
|
|
992
|
+
return false;
|
|
993
|
+
return !!ts3.getModifiers(statement)?.some((modifier) => modifier.kind === ts3.SyntaxKind.DefaultKeyword);
|
|
994
|
+
}
|
|
995
|
+
function getBangCommentWarnings(sourceFile) {
|
|
996
|
+
if (!isClientReachableFile(sourceFile.file))
|
|
997
|
+
return [];
|
|
998
|
+
return findPatternLines(sourceFile.content, /(?:^|[^\s/][ \t]+)\/[*/]!/).map((line) => ({
|
|
999
|
+
rule: "akan.file.bang-comment-in-client",
|
|
1000
|
+
scope: "file",
|
|
1001
|
+
severity: "warning",
|
|
1002
|
+
file: sourceFile.file,
|
|
1003
|
+
line,
|
|
1004
|
+
message: "A `//!` or `/*!` marker survives minification and ships to the browser."
|
|
1005
|
+
}));
|
|
1006
|
+
}
|
|
1007
|
+
function isClientReachableFile(file) {
|
|
1008
|
+
if (/\.(test|spec)\.tsx?$/.test(file))
|
|
1009
|
+
return false;
|
|
1010
|
+
if (/\.(constant|store)\.ts$/.test(file))
|
|
1011
|
+
return true;
|
|
1012
|
+
if (/\.(Template|Unit|Util|View|Zone)\.tsx$/.test(file))
|
|
1013
|
+
return true;
|
|
1014
|
+
const segments = file.split("/");
|
|
1015
|
+
if (segments.includes("ui") || segments.includes("webkit"))
|
|
1016
|
+
return /\.tsx?$/.test(file);
|
|
1017
|
+
if (segments.includes("common"))
|
|
1018
|
+
return file.endsWith(".ts");
|
|
1019
|
+
return segments.includes("page") && file.endsWith(".tsx");
|
|
941
1020
|
}
|
|
942
1021
|
function getPlaceholderExportWarnings(sourceFile) {
|
|
943
1022
|
if (!sourceFile.file.endsWith("/index.ts") && !sourceFile.file.endsWith("/index.tsx"))
|
|
@@ -967,7 +1046,7 @@ function getDictionaryTextWarnings(sourceFile) {
|
|
|
967
1046
|
function getGlobalMutationWarnings(sourceFile) {
|
|
968
1047
|
const warnings = [];
|
|
969
1048
|
for (const statement of sourceFile.sourceFile.statements) {
|
|
970
|
-
if (
|
|
1049
|
+
if (ts3.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global") {
|
|
971
1050
|
warnings.push({
|
|
972
1051
|
rule: "akan.file.global-declaration",
|
|
973
1052
|
scope: "file",
|
|
@@ -977,7 +1056,7 @@ function getGlobalMutationWarnings(sourceFile) {
|
|
|
977
1056
|
message: "Global declarations require an explicit low-level integration allowlist."
|
|
978
1057
|
});
|
|
979
1058
|
}
|
|
980
|
-
if (
|
|
1059
|
+
if (ts3.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
|
|
981
1060
|
warnings.push({
|
|
982
1061
|
rule: "akan.file.window-augmentation",
|
|
983
1062
|
scope: "file",
|
|
@@ -987,7 +1066,7 @@ function getGlobalMutationWarnings(sourceFile) {
|
|
|
987
1066
|
message: "Window augmentation should be isolated to approved browser integration files."
|
|
988
1067
|
});
|
|
989
1068
|
}
|
|
990
|
-
if (
|
|
1069
|
+
if (ts3.isExpressionStatement(statement) && statement.expression.getText(sourceFile.sourceFile).includes(".prototype.")) {
|
|
991
1070
|
warnings.push({
|
|
992
1071
|
rule: "akan.file.prototype-mutation",
|
|
993
1072
|
scope: "file",
|
|
@@ -1005,23 +1084,23 @@ function getTopLevelDeclarations(sourceFile) {
|
|
|
1005
1084
|
}
|
|
1006
1085
|
function getTopLevelDeclaration(sourceFile, statement) {
|
|
1007
1086
|
const line = getLine(sourceFile, statement);
|
|
1008
|
-
if (
|
|
1087
|
+
if (ts3.isClassDeclaration(statement) && statement.name) {
|
|
1009
1088
|
return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
|
|
1010
1089
|
}
|
|
1011
|
-
if (
|
|
1090
|
+
if (ts3.isFunctionDeclaration(statement) && statement.name) {
|
|
1012
1091
|
return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
|
|
1013
1092
|
}
|
|
1014
|
-
if (
|
|
1093
|
+
if (ts3.isInterfaceDeclaration(statement)) {
|
|
1015
1094
|
return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
|
|
1016
1095
|
}
|
|
1017
|
-
if (
|
|
1096
|
+
if (ts3.isTypeAliasDeclaration(statement)) {
|
|
1018
1097
|
return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
|
|
1019
1098
|
}
|
|
1020
|
-
if (
|
|
1099
|
+
if (ts3.isEnumDeclaration(statement)) {
|
|
1021
1100
|
return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
|
|
1022
1101
|
}
|
|
1023
|
-
if (
|
|
1024
|
-
return statement.declarationList.declarations.filter((declaration) =>
|
|
1102
|
+
if (ts3.isVariableStatement(statement)) {
|
|
1103
|
+
return statement.declarationList.declarations.filter((declaration) => ts3.isIdentifier(declaration.name)).map((declaration) => ({
|
|
1025
1104
|
name: declaration.name.text,
|
|
1026
1105
|
kind: "variable",
|
|
1027
1106
|
line: getLine(sourceFile, declaration),
|
|
@@ -1029,7 +1108,7 @@ function getTopLevelDeclaration(sourceFile, statement) {
|
|
|
1029
1108
|
node: statement
|
|
1030
1109
|
}));
|
|
1031
1110
|
}
|
|
1032
|
-
if (
|
|
1111
|
+
if (ts3.isExportDeclaration(statement)) {
|
|
1033
1112
|
return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
|
|
1034
1113
|
}
|
|
1035
1114
|
return [];
|
|
@@ -1054,9 +1133,9 @@ function isAllowedConstantDeclaration(modelName, declaration) {
|
|
|
1054
1133
|
return false;
|
|
1055
1134
|
if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
|
|
1056
1135
|
return true;
|
|
1057
|
-
if (!
|
|
1136
|
+
if (!ts3.isClassDeclaration(declaration.node))
|
|
1058
1137
|
return false;
|
|
1059
|
-
const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token ===
|
|
1138
|
+
const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts3.SyntaxKind.ExtendsKeyword);
|
|
1060
1139
|
const expression = heritageClause?.types[0]?.expression;
|
|
1061
1140
|
return !!expression && expression.getText().startsWith("enumOf(");
|
|
1062
1141
|
}
|
|
@@ -1073,7 +1152,19 @@ function getConventionDescription(suffix, modelName) {
|
|
|
1073
1152
|
return `${modelName}Internal, ${modelName}Slice, or ${modelName}Endpoint`;
|
|
1074
1153
|
return `${modelName}Store`;
|
|
1075
1154
|
}
|
|
1076
|
-
|
|
1155
|
+
var sysRootTypes = { apps: "app", libs: "lib" };
|
|
1156
|
+
function getSysRootEntry(file) {
|
|
1157
|
+
const segments = file.split("/");
|
|
1158
|
+
const root = segments[0];
|
|
1159
|
+
const name = segments[2];
|
|
1160
|
+
if (!root || !name || segments.length < 3)
|
|
1161
|
+
return null;
|
|
1162
|
+
const type = sysRootTypes[root];
|
|
1163
|
+
if (!type)
|
|
1164
|
+
return null;
|
|
1165
|
+
return { type, name, isDir: segments.length > 3 };
|
|
1166
|
+
}
|
|
1167
|
+
function getLibFacetRootFile(file) {
|
|
1077
1168
|
const segments = file.split("/");
|
|
1078
1169
|
if ((segments[0] === "libs" || segments[0] === "apps") && segments.length === 4 && segments[2] === "lib")
|
|
1079
1170
|
return segments[3];
|
|
@@ -1122,13 +1213,13 @@ function getModuleInfo(file) {
|
|
|
1122
1213
|
return { moduleName, fileName, kind: "database" };
|
|
1123
1214
|
}
|
|
1124
1215
|
function isExportedConst(declaration) {
|
|
1125
|
-
return declaration.exported &&
|
|
1216
|
+
return declaration.exported && ts3.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts3.NodeFlags.Const) !== 0;
|
|
1126
1217
|
}
|
|
1127
1218
|
function isExported(node) {
|
|
1128
|
-
return !!
|
|
1219
|
+
return !!ts3.getCombinedModifierFlags(node) && !!(ts3.getCombinedModifierFlags(node) & ts3.ModifierFlags.Export);
|
|
1129
1220
|
}
|
|
1130
1221
|
function isFunctionLikeInitializer(node) {
|
|
1131
|
-
return !!node && (
|
|
1222
|
+
return !!node && (ts3.isArrowFunction(node) || ts3.isFunctionExpression(node));
|
|
1132
1223
|
}
|
|
1133
1224
|
function getBodyFingerprint(sourceFile, node) {
|
|
1134
1225
|
if (!node)
|
|
@@ -1150,7 +1241,7 @@ async function isDirectory(absolutePath) {
|
|
|
1150
1241
|
}
|
|
1151
1242
|
}
|
|
1152
1243
|
function getScriptKind(file) {
|
|
1153
|
-
return file.endsWith(".tsx") ?
|
|
1244
|
+
return file.endsWith(".tsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS;
|
|
1154
1245
|
}
|
|
1155
1246
|
function getLine(sourceFile, node) {
|
|
1156
1247
|
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|