@akanjs/cli 3.0.0-alpha.14 → 3.0.0-alpha.16
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/{agent.command-zxaxy577.js → agent.command-h6tc0pzh.js} +5 -5
- package/{application.command-an05acvc.js → application.command-kphnfwyg.js} +4 -4
- package/buildBatch.proc.js +1 -1
- package/{capacitorApp-5q8ytkqw.js → capacitorApp-9ejk3k7q.js} +2 -2
- package/{cloud.command-yfd3pwr1.js → cloud.command-zn6mfscb.js} +7 -7
- package/{context.command-03b3jgab.js → context.command-pkbccyys.js} +12 -12
- package/{guideline.command-5qnjcgmk.js → guideline.command-0jj2k77g.js} +2 -2
- package/guidelines/conventions/conventions.instruction.md +39 -38
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +1 -3
- package/incrementalBuilder.proc.js +1 -1
- package/{index-3tqjdv03.js → index-1twehz1x.js} +23 -3
- package/{index-37mwgmp7.js → index-4agbdds8.js} +3 -3
- package/{index-jwvs48kc.js → index-4v0wvt37.js} +4 -4
- package/{index-xv5jv1fe.js → index-7ecpft60.js} +6 -3
- package/{index-6xh65612.js → index-8d9r0df5.js} +2 -2
- package/{index-hz5x5zdp.js → index-96vpa123.js} +3 -3
- package/{index-nz9ceqgp.js → index-9s991k9v.js} +1 -1
- package/{index-nmfen65g.js → index-aak9cctp.js} +4 -4
- package/{index-0hk3frny.js → index-b9m84bjp.js} +10 -10
- package/{index-9p2edf9w.js → index-bkwnr08k.js} +1 -1
- package/{index-znxjcx1p.js → index-djh5gpb7.js} +2 -2
- package/{index-pgs252a3.js → index-g3brq942.js} +5 -5
- package/{index-tk2e3m3y.js → index-gyvrhtpm.js} +1 -1
- package/{index-pdj7r8fa.js → index-gz91pc3p.js} +1 -1
- package/{index-nkzfkq0j.js → index-p42te6qb.js} +3 -3
- package/{index-v2r1wrzb.js → index-rv16ck4r.js} +4 -4
- package/{index-w7jphpt6.js → index-w7farjxd.js} +3 -3
- package/{index-9jwc3ew8.js → index-xwfkgss0.js} +2 -2
- package/index.js +18 -18
- package/{library.command-befyckb7.js → library.command-wbptp6q3.js} +3 -3
- package/{localRegistry.command-fkd3xw99.js → localRegistry.command-6ea3pgq0.js} +6 -6
- package/{module.command-xzjbbas7.js → module.command-ghkf919z.js} +5 -5
- package/{package.command-9xk8ye7a.js → package.command-ph2m34tv.js} +3 -3
- package/package.json +2 -2
- package/{page.command-4g2xszh7.js → page.command-atnzfn88.js} +3 -3
- package/{primitive.command-ffwn141j.js → primitive.command-7qbdhfc2.js} +6 -6
- package/{quality.command-8e6bhm55.js → quality.command-51q9kmhj.js} +90 -361
- package/{repair.command-w3tjhr9r.js → repair.command-weakn0yr.js} +5 -5
- package/{scalar.command-kj121mby.js → scalar.command-xdjhvsgb.js} +4 -4
- package/templates/appSample/srvkit/AuthGuard.ts +2 -2
- package/templates/workspaceRoot/CLAUDE.md.template +19 -0
- package/{workflow.command-mnkm1bwa.js → workflow.command-msm2tjee.js} +9 -9
- package/{workspace.command-waxe7ts7.js → workspace.command-gr8z82z9.js} +18 -18
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
command,
|
|
8
8
|
runner,
|
|
9
9
|
script
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-bkwnr08k.js";
|
|
11
11
|
import {
|
|
12
12
|
appRootAllowedFiles,
|
|
13
13
|
libFacetRootAllowedFiles
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-1twehz1x.js";
|
|
15
15
|
import"./index-mxvakhsm.js";
|
|
16
16
|
import"./index-1577bej2.js";
|
|
17
17
|
import"./index-67546d0j.js";
|
|
@@ -20,163 +20,13 @@ import"./index-r24hmh0q.js";
|
|
|
20
20
|
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
21
21
|
import { createHash } from "crypto";
|
|
22
22
|
import { readdir, readFile, stat } from "fs/promises";
|
|
23
|
-
import
|
|
23
|
+
import path from "path";
|
|
24
24
|
import { RESERVED_ROUTE_CONFIG_EXPORTS } from "akanjs/common";
|
|
25
25
|
import ignore from "ignore";
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
// pkgs/@akanjs/devkit/mcpScanner.ts
|
|
29
|
-
import path from "path";
|
|
30
|
-
import ts from "typescript";
|
|
31
|
-
|
|
32
|
-
class McpScanner {
|
|
33
|
-
scan(sourceFiles) {
|
|
34
|
-
const dictionaries = new Map(sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".dictionary.ts")).map((sourceFile) => [path.dirname(sourceFile.file), sourceFile]));
|
|
35
|
-
return sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".signal.ts")).flatMap((sourceFile) => this.#scanSignal(sourceFile, dictionaries.get(path.dirname(sourceFile.file))));
|
|
36
|
-
}
|
|
37
|
-
#scanSignal(signal, dictionary) {
|
|
38
|
-
const exposed = McpScanner.#exposedDeclarations(signal);
|
|
39
|
-
if (!exposed.length)
|
|
40
|
-
return [];
|
|
41
|
-
const refName = path.basename(signal.file, ".signal.ts").replace(/^_+/, "");
|
|
42
|
-
const described = dictionary ? McpScanner.#describedEntries(dictionary) : new Map;
|
|
43
|
-
return [
|
|
44
|
-
...exposed.filter(({ name, kind }) => !McpScanner.#isDescribed(described, refName, name, kind)).map(({ name, kind, line }) => ({
|
|
45
|
-
rule: "akan.mcp.missing-description",
|
|
46
|
-
scope: "mcp",
|
|
47
|
-
severity: "warning",
|
|
48
|
-
file: signal.file,
|
|
49
|
-
line,
|
|
50
|
-
message: `MCP-exposed ${kind} "${name}" has no dictionary .desc(); an agent sees its name and nothing else.`
|
|
51
|
-
})),
|
|
52
|
-
...exposed.filter(({ guards }) => guards === "missing").map(({ name, kind, line }) => ({
|
|
53
|
-
rule: "akan.mcp.unguarded-exposure",
|
|
54
|
-
scope: "mcp",
|
|
55
|
-
severity: "warning",
|
|
56
|
-
file: signal.file,
|
|
57
|
-
line,
|
|
58
|
-
message: `MCP-exposed ${kind} "${name}" declares no guards; a slice's guards map never reaches a named slice.`
|
|
59
|
-
}))
|
|
60
|
-
];
|
|
61
|
-
}
|
|
62
|
-
static #isDescribed(described, refName, name, kind) {
|
|
63
|
-
if (described.get(kind)?.has(name))
|
|
64
|
-
return true;
|
|
65
|
-
return kind === "slice" && !!described.get("endpoint")?.has(`${refName}List${McpScanner.#capitalize(name)}`);
|
|
66
|
-
}
|
|
67
|
-
static #exposedDeclarations(signal) {
|
|
68
|
-
const found = [];
|
|
69
|
-
const visit = (node) => {
|
|
70
|
-
if (McpScanner.#isExposeOption(node)) {
|
|
71
|
-
const declaration = McpScanner.#enclosingDeclaration(node, signal.sourceFile);
|
|
72
|
-
if (declaration)
|
|
73
|
-
found.push(declaration);
|
|
74
|
-
}
|
|
75
|
-
ts.forEachChild(node, visit);
|
|
76
|
-
};
|
|
77
|
-
visit(signal.sourceFile);
|
|
78
|
-
return found;
|
|
79
|
-
}
|
|
80
|
-
static #isExposeOption(node) {
|
|
81
|
-
if (!ts.isPropertyAssignment(node) || McpScanner.#propertyName(node) !== "mcp")
|
|
82
|
-
return false;
|
|
83
|
-
if (!ts.isObjectLiteralExpression(node.initializer))
|
|
84
|
-
return false;
|
|
85
|
-
return node.initializer.properties.some((property) => ts.isPropertyAssignment(property) && McpScanner.#propertyName(property) === "expose" && property.initializer.kind === ts.SyntaxKind.TrueKeyword);
|
|
86
|
-
}
|
|
87
|
-
static #enclosingDeclaration(option, sourceFile) {
|
|
88
|
-
let declaration = null;
|
|
89
|
-
for (let node = option.parent;node; node = node.parent) {
|
|
90
|
-
if (!declaration && ts.isPropertyAssignment(node)) {
|
|
91
|
-
declaration = node;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const kind = McpScanner.#factoryKind(node);
|
|
95
|
-
if (!kind || !declaration)
|
|
96
|
-
continue;
|
|
97
|
-
const name = McpScanner.#propertyName(declaration);
|
|
98
|
-
if (!name)
|
|
99
|
-
return null;
|
|
100
|
-
const line = sourceFile.getLineAndCharacterOfPosition(declaration.getStart(sourceFile)).line + 1;
|
|
101
|
-
return { name, kind, line, guards: McpScanner.#guardState(option) };
|
|
102
|
-
}
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
static #guardState(option) {
|
|
106
|
-
const options = option.parent;
|
|
107
|
-
if (!ts.isObjectLiteralExpression(options))
|
|
108
|
-
return "unknown";
|
|
109
|
-
if (options.properties.some((property) => ts.isSpreadAssignment(property)))
|
|
110
|
-
return "unknown";
|
|
111
|
-
return options.properties.some((property) => ts.isPropertyAssignment(property) && McpScanner.#propertyName(property) === "guards") ? "declared" : "missing";
|
|
112
|
-
}
|
|
113
|
-
static #factoryKind(node) {
|
|
114
|
-
if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression))
|
|
115
|
-
return null;
|
|
116
|
-
const factory = node.expression.text;
|
|
117
|
-
return factory === "slice" || factory === "endpoint" ? factory : null;
|
|
118
|
-
}
|
|
119
|
-
static #describedEntries(dictionary) {
|
|
120
|
-
const described = new Map;
|
|
121
|
-
const visit = (node) => {
|
|
122
|
-
const stage = McpScanner.#dictionaryStage(node);
|
|
123
|
-
if (stage) {
|
|
124
|
-
for (const [name, chain] of McpScanner.#stageEntries(node)) {
|
|
125
|
-
if (!chain.has("desc"))
|
|
126
|
-
continue;
|
|
127
|
-
const names = described.get(stage) ?? new Set;
|
|
128
|
-
names.add(name);
|
|
129
|
-
described.set(stage, names);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
ts.forEachChild(node, visit);
|
|
133
|
-
};
|
|
134
|
-
visit(dictionary.sourceFile);
|
|
135
|
-
return described;
|
|
136
|
-
}
|
|
137
|
-
static #dictionaryStage(node) {
|
|
138
|
-
if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression))
|
|
139
|
-
return null;
|
|
140
|
-
const stage = node.expression.name.text;
|
|
141
|
-
return stage === "endpoint" || stage === "slice" ? stage : null;
|
|
142
|
-
}
|
|
143
|
-
static #stageEntries(stage) {
|
|
144
|
-
const callback = stage.arguments[0];
|
|
145
|
-
if (!callback || !ts.isArrowFunction(callback))
|
|
146
|
-
return [];
|
|
147
|
-
const body = ts.isParenthesizedExpression(callback.body) ? callback.body.expression : callback.body;
|
|
148
|
-
if (!ts.isObjectLiteralExpression(body))
|
|
149
|
-
return [];
|
|
150
|
-
return body.properties.flatMap((property) => {
|
|
151
|
-
if (!ts.isPropertyAssignment(property))
|
|
152
|
-
return [];
|
|
153
|
-
const name = McpScanner.#propertyName(property);
|
|
154
|
-
return name ? [[name, McpScanner.#chainCalls(property.initializer)]] : [];
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
static #chainCalls(expression) {
|
|
158
|
-
const calls = new Set;
|
|
159
|
-
let current = expression;
|
|
160
|
-
while (ts.isCallExpression(current) || ts.isPropertyAccessExpression(current)) {
|
|
161
|
-
if (ts.isPropertyAccessExpression(current))
|
|
162
|
-
calls.add(current.name.text);
|
|
163
|
-
current = current.expression;
|
|
164
|
-
}
|
|
165
|
-
return calls;
|
|
166
|
-
}
|
|
167
|
-
static #propertyName(property) {
|
|
168
|
-
const { name } = property;
|
|
169
|
-
if (ts.isIdentifier(name) || ts.isStringLiteral(name))
|
|
170
|
-
return name.text;
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
static #capitalize(value) {
|
|
174
|
-
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
26
|
+
import ts2 from "typescript";
|
|
177
27
|
|
|
178
28
|
// pkgs/@akanjs/devkit/ssrScanner.ts
|
|
179
|
-
import
|
|
29
|
+
import ts from "typescript";
|
|
180
30
|
var STATIC_COMPONENT_MIN_MASS = 4;
|
|
181
31
|
var MIXED_COMPONENT_MIN_MASS = 10;
|
|
182
32
|
var MIXED_COMPONENT_MAX_TOUCHES = 2;
|
|
@@ -294,31 +144,31 @@ class SsrScanner {
|
|
|
294
144
|
});
|
|
295
145
|
}
|
|
296
146
|
}
|
|
297
|
-
|
|
147
|
+
ts.forEachChild(node, visit);
|
|
298
148
|
};
|
|
299
|
-
|
|
149
|
+
ts.forEachChild(sourceFile.sourceFile, visit);
|
|
300
150
|
return warnings;
|
|
301
151
|
}
|
|
302
152
|
#isMountEffect(sourceFile, node) {
|
|
303
|
-
if (!
|
|
153
|
+
if (!ts.isCallExpression(node))
|
|
304
154
|
return false;
|
|
305
155
|
const callee = node.expression.getText(sourceFile);
|
|
306
156
|
if (callee !== "useEffect" && callee !== "useLayoutEffect")
|
|
307
157
|
return false;
|
|
308
158
|
const deps = node.arguments[1];
|
|
309
|
-
return !!deps &&
|
|
159
|
+
return !!deps && ts.isArrayLiteralExpression(deps) && deps.elements.length === 0;
|
|
310
160
|
}
|
|
311
161
|
#getLoadCalls(sourceFile, node) {
|
|
312
162
|
const calls = [];
|
|
313
163
|
const visit = (child) => {
|
|
314
|
-
if (
|
|
164
|
+
if (ts.isCallExpression(child)) {
|
|
315
165
|
const callee = child.expression.getText(sourceFile);
|
|
316
166
|
if (/^fetch\.[a-z]/.test(callee) || /^st\.do\.(init|get|view|load|list|count|insight)[A-Z]/.test(callee))
|
|
317
167
|
calls.push({ callee, node: child });
|
|
318
168
|
}
|
|
319
|
-
|
|
169
|
+
ts.forEachChild(child, visit);
|
|
320
170
|
};
|
|
321
|
-
|
|
171
|
+
ts.forEachChild(node, visit);
|
|
322
172
|
return calls;
|
|
323
173
|
}
|
|
324
174
|
#getTemplateStateWarnings(sourceFile) {
|
|
@@ -326,7 +176,7 @@ class SsrScanner {
|
|
|
326
176
|
return [];
|
|
327
177
|
const warnings = [];
|
|
328
178
|
const visit = (node) => {
|
|
329
|
-
if (
|
|
179
|
+
if (ts.isCallExpression(node) && node.expression.getText(sourceFile.sourceFile) === "useState") {
|
|
330
180
|
warnings.push({
|
|
331
181
|
rule: "akan.ssr.template-client-state",
|
|
332
182
|
scope: "ssr",
|
|
@@ -336,9 +186,9 @@ class SsrScanner {
|
|
|
336
186
|
message: "Template holds form state in useState. Templates are store-driven and carry no local state."
|
|
337
187
|
});
|
|
338
188
|
}
|
|
339
|
-
|
|
189
|
+
ts.forEachChild(node, visit);
|
|
340
190
|
};
|
|
341
|
-
|
|
191
|
+
ts.forEachChild(sourceFile.sourceFile, visit);
|
|
342
192
|
return warnings;
|
|
343
193
|
}
|
|
344
194
|
#measureBalance(sourceFiles) {
|
|
@@ -379,14 +229,14 @@ class SsrScanner {
|
|
|
379
229
|
return components;
|
|
380
230
|
}
|
|
381
231
|
#getComponentNodes(statement) {
|
|
382
|
-
if (
|
|
232
|
+
if (ts.isFunctionDeclaration(statement) && statement.body)
|
|
383
233
|
return [{ name: statement.name?.text ?? "default", node: statement.body }];
|
|
384
|
-
if (!
|
|
234
|
+
if (!ts.isVariableStatement(statement))
|
|
385
235
|
return [];
|
|
386
236
|
return statement.declarationList.declarations.flatMap((declaration) => {
|
|
387
|
-
if (!
|
|
237
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
|
|
388
238
|
return [];
|
|
389
|
-
if (!
|
|
239
|
+
if (!ts.isArrowFunction(declaration.initializer) && !ts.isFunctionExpression(declaration.initializer))
|
|
390
240
|
return [];
|
|
391
241
|
return [{ name: declaration.name.text, node: declaration.initializer }];
|
|
392
242
|
});
|
|
@@ -394,7 +244,7 @@ class SsrScanner {
|
|
|
394
244
|
#getTouches(sourceFile, node) {
|
|
395
245
|
const touches = [];
|
|
396
246
|
const visit = (child) => {
|
|
397
|
-
if (
|
|
247
|
+
if (ts.isCallExpression(child)) {
|
|
398
248
|
const callee = child.expression.getText(sourceFile);
|
|
399
249
|
const bareName = callee.split(".").pop() ?? callee;
|
|
400
250
|
if (callee === "createContext" || callee === "lazy")
|
|
@@ -402,60 +252,60 @@ class SsrScanner {
|
|
|
402
252
|
else if (/^use[A-Z]/.test(bareName) && !SsrScanner.#serverSafeCalls.has(bareName))
|
|
403
253
|
touches.push(bareName);
|
|
404
254
|
}
|
|
405
|
-
if (
|
|
255
|
+
if (ts.isJsxAttribute(child) && /^on[A-Z]/.test(child.name.getText(sourceFile)))
|
|
406
256
|
touches.push(child.name.getText(sourceFile));
|
|
407
|
-
if (
|
|
257
|
+
if (ts.isPropertyAccessExpression(child)) {
|
|
408
258
|
const root = getAccessRoot(child);
|
|
409
259
|
if (root === "st")
|
|
410
260
|
touches.push("st");
|
|
411
261
|
else if (SsrScanner.#clientGlobals.has(root))
|
|
412
262
|
touches.push(root);
|
|
413
263
|
}
|
|
414
|
-
|
|
264
|
+
ts.forEachChild(child, visit);
|
|
415
265
|
};
|
|
416
|
-
|
|
266
|
+
ts.forEachChild(node, visit);
|
|
417
267
|
return touches;
|
|
418
268
|
}
|
|
419
269
|
#getTagNames(sourceFile, node) {
|
|
420
270
|
const tags = new Set;
|
|
421
271
|
const visit = (child) => {
|
|
422
|
-
if (
|
|
272
|
+
if (ts.isJsxOpeningElement(child) || ts.isJsxSelfClosingElement(child))
|
|
423
273
|
tags.add(child.tagName.getText(sourceFile).split(".")[0]);
|
|
424
|
-
|
|
274
|
+
ts.forEachChild(child, visit);
|
|
425
275
|
};
|
|
426
|
-
|
|
276
|
+
ts.forEachChild(node, visit);
|
|
427
277
|
return tags;
|
|
428
278
|
}
|
|
429
279
|
#getMass(node) {
|
|
430
280
|
let mass = 0;
|
|
431
281
|
const visit = (child) => {
|
|
432
|
-
if (
|
|
282
|
+
if (ts.isJsxOpeningElement(child) || ts.isJsxSelfClosingElement(child))
|
|
433
283
|
mass += 1;
|
|
434
|
-
|
|
284
|
+
ts.forEachChild(child, visit);
|
|
435
285
|
};
|
|
436
|
-
|
|
286
|
+
ts.forEachChild(node, visit);
|
|
437
287
|
return mass;
|
|
438
288
|
}
|
|
439
289
|
#hasUseClient(sourceFile) {
|
|
440
290
|
const first = sourceFile.statements[0];
|
|
441
|
-
if (!first || !
|
|
291
|
+
if (!first || !ts.isExpressionStatement(first) || !ts.isStringLiteral(first.expression))
|
|
442
292
|
return false;
|
|
443
293
|
return first.expression.text === "use client";
|
|
444
294
|
}
|
|
445
295
|
#hasVendorImport(sourceFile) {
|
|
446
|
-
return sourceFile.statements.some((statement) =>
|
|
296
|
+
return sourceFile.statements.some((statement) => ts.isImportDeclaration(statement) && isVendorSpecifier(getSpecifier(statement)));
|
|
447
297
|
}
|
|
448
298
|
#getVendorNames(sourceFile) {
|
|
449
299
|
const names = new Set;
|
|
450
300
|
for (const statement of sourceFile.statements) {
|
|
451
|
-
if (!
|
|
301
|
+
if (!ts.isImportDeclaration(statement) || !isVendorSpecifier(getSpecifier(statement)))
|
|
452
302
|
continue;
|
|
453
303
|
const clause = statement.importClause;
|
|
454
304
|
if (clause?.name)
|
|
455
305
|
names.add(clause.name.text);
|
|
456
|
-
if (clause?.namedBindings &&
|
|
306
|
+
if (clause?.namedBindings && ts.isNamespaceImport(clause.namedBindings))
|
|
457
307
|
names.add(clause.namedBindings.name.text);
|
|
458
|
-
if (clause?.namedBindings &&
|
|
308
|
+
if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings))
|
|
459
309
|
for (const element of clause.namedBindings.elements)
|
|
460
310
|
names.add(element.name.text);
|
|
461
311
|
}
|
|
@@ -463,10 +313,10 @@ class SsrScanner {
|
|
|
463
313
|
}
|
|
464
314
|
#importsClientRuntime(sourceFile) {
|
|
465
315
|
for (const statement of sourceFile.statements) {
|
|
466
|
-
if (!
|
|
316
|
+
if (!ts.isImportDeclaration(statement))
|
|
467
317
|
continue;
|
|
468
318
|
const bindings = statement.importClause?.namedBindings;
|
|
469
|
-
if (!bindings || !
|
|
319
|
+
if (!bindings || !ts.isNamedImports(bindings))
|
|
470
320
|
continue;
|
|
471
321
|
if (bindings.elements.some((element) => SsrScanner.#clientRuntimeImports.has(element.name.text)))
|
|
472
322
|
return true;
|
|
@@ -517,7 +367,7 @@ function getShare(serverMass, clientMass) {
|
|
|
517
367
|
return total === 0 ? 1 : serverMass / total;
|
|
518
368
|
}
|
|
519
369
|
function getSpecifier(statement) {
|
|
520
|
-
return
|
|
370
|
+
return ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
|
|
521
371
|
}
|
|
522
372
|
function isVendorSpecifier(specifier) {
|
|
523
373
|
if (specifier === "" || specifier.startsWith(".") || specifier.startsWith("/"))
|
|
@@ -530,125 +380,9 @@ function isVendorSpecifier(specifier) {
|
|
|
530
380
|
}
|
|
531
381
|
function getAccessRoot(node) {
|
|
532
382
|
let current = node;
|
|
533
|
-
while (
|
|
383
|
+
while (ts.isPropertyAccessExpression(current))
|
|
534
384
|
current = current.expression;
|
|
535
|
-
return
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// pkgs/@akanjs/devkit/storeScanner.ts
|
|
539
|
-
import path2 from "path";
|
|
540
|
-
import ts3 from "typescript";
|
|
541
|
-
|
|
542
|
-
class StoreScanner {
|
|
543
|
-
scan(sourceFiles) {
|
|
544
|
-
const dictionaries = new Map(sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".dictionary.ts")).map((sourceFile) => [path2.dirname(sourceFile.file), sourceFile]));
|
|
545
|
-
return sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".store.ts")).flatMap((sourceFile) => this.#scanStore(sourceFile, dictionaries.get(path2.dirname(sourceFile.file))));
|
|
546
|
-
}
|
|
547
|
-
#scanStore(store, dictionary) {
|
|
548
|
-
const actions = StoreScanner.#customActions(store);
|
|
549
|
-
if (!actions.length)
|
|
550
|
-
return [];
|
|
551
|
-
const described = dictionary ? StoreScanner.#describedEntries(dictionary) : new Map;
|
|
552
|
-
return actions.filter(({ name, fetched }) => fetched.length && !fetched.includes(name)).filter(({ name }) => !described.get("store")?.has(name) && !described.get("endpoint")?.has(name)).map(({ name, line, fetched }) => ({
|
|
553
|
-
rule: "akan.agent.missing-store-description",
|
|
554
|
-
scope: "agent",
|
|
555
|
-
severity: "warning",
|
|
556
|
-
file: store.file,
|
|
557
|
-
line,
|
|
558
|
-
message: `Store action "${name}" calls ${fetched.map((key) => `${key}()`).join(", ")} under a different name and has no dictionary .store() entry, so an agent reading it has the name and nothing else.`
|
|
559
|
-
}));
|
|
560
|
-
}
|
|
561
|
-
static #customActions(store) {
|
|
562
|
-
const actions = [];
|
|
563
|
-
const visit = (node) => {
|
|
564
|
-
if (ts3.isClassDeclaration(node) && StoreScanner.#extendsStore(node)) {
|
|
565
|
-
for (const member of node.members) {
|
|
566
|
-
if (!ts3.isMethodDeclaration(member))
|
|
567
|
-
continue;
|
|
568
|
-
if (member.modifiers?.some((modifier) => modifier.kind === ts3.SyntaxKind.StaticKeyword))
|
|
569
|
-
continue;
|
|
570
|
-
const name = StoreScanner.#memberName(member);
|
|
571
|
-
if (!name)
|
|
572
|
-
continue;
|
|
573
|
-
const line = store.sourceFile.getLineAndCharacterOfPosition(member.getStart(store.sourceFile)).line + 1;
|
|
574
|
-
actions.push({ name, line, fetched: StoreScanner.#fetchedEndpoints(member) });
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
ts3.forEachChild(node, visit);
|
|
578
|
-
};
|
|
579
|
-
visit(store.sourceFile);
|
|
580
|
-
return actions;
|
|
581
|
-
}
|
|
582
|
-
static #extendsStore(node) {
|
|
583
|
-
return !!node.heritageClauses?.some((clause) => clause.types.some((type) => ts3.isCallExpression(type.expression) && StoreScanner.#isStoreCall(type.expression)));
|
|
584
|
-
}
|
|
585
|
-
static #isStoreCall(expression) {
|
|
586
|
-
return ts3.isIdentifier(expression.expression) && expression.expression.text === "store";
|
|
587
|
-
}
|
|
588
|
-
static #fetchedEndpoints(member) {
|
|
589
|
-
const fetched = new Set;
|
|
590
|
-
const visit = (node) => {
|
|
591
|
-
if (ts3.isCallExpression(node) && ts3.isPropertyAccessExpression(node.expression) && ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "fetch")
|
|
592
|
-
fetched.add(node.expression.name.text);
|
|
593
|
-
ts3.forEachChild(node, visit);
|
|
594
|
-
};
|
|
595
|
-
visit(member);
|
|
596
|
-
return [...fetched];
|
|
597
|
-
}
|
|
598
|
-
static #describedEntries(dictionary) {
|
|
599
|
-
const described = new Map;
|
|
600
|
-
const visit = (node) => {
|
|
601
|
-
const stage = StoreScanner.#dictionaryStage(node);
|
|
602
|
-
if (stage) {
|
|
603
|
-
for (const [name, chain] of StoreScanner.#stageEntries(node)) {
|
|
604
|
-
if (!chain.has("desc"))
|
|
605
|
-
continue;
|
|
606
|
-
const names = described.get(stage) ?? new Set;
|
|
607
|
-
names.add(name);
|
|
608
|
-
described.set(stage, names);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
ts3.forEachChild(node, visit);
|
|
612
|
-
};
|
|
613
|
-
visit(dictionary.sourceFile);
|
|
614
|
-
return described;
|
|
615
|
-
}
|
|
616
|
-
static #dictionaryStage(node) {
|
|
617
|
-
if (!ts3.isCallExpression(node) || !ts3.isPropertyAccessExpression(node.expression))
|
|
618
|
-
return null;
|
|
619
|
-
const stage = node.expression.name.text;
|
|
620
|
-
return stage === "store" || stage === "endpoint" ? stage : null;
|
|
621
|
-
}
|
|
622
|
-
static #stageEntries(stage) {
|
|
623
|
-
const callback = stage.arguments[0];
|
|
624
|
-
if (!callback || !ts3.isArrowFunction(callback))
|
|
625
|
-
return [];
|
|
626
|
-
const body = ts3.isParenthesizedExpression(callback.body) ? callback.body.expression : callback.body;
|
|
627
|
-
if (!ts3.isObjectLiteralExpression(body))
|
|
628
|
-
return [];
|
|
629
|
-
return body.properties.flatMap((property) => {
|
|
630
|
-
if (!ts3.isPropertyAssignment(property))
|
|
631
|
-
return [];
|
|
632
|
-
const name = StoreScanner.#memberName(property);
|
|
633
|
-
return name ? [[name, StoreScanner.#chainCalls(property.initializer)]] : [];
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
static #chainCalls(expression) {
|
|
637
|
-
const calls = new Set;
|
|
638
|
-
let current = expression;
|
|
639
|
-
while (ts3.isCallExpression(current) || ts3.isPropertyAccessExpression(current)) {
|
|
640
|
-
if (ts3.isPropertyAccessExpression(current))
|
|
641
|
-
calls.add(current.name.text);
|
|
642
|
-
current = current.expression;
|
|
643
|
-
}
|
|
644
|
-
return calls;
|
|
645
|
-
}
|
|
646
|
-
static #memberName(member) {
|
|
647
|
-
const { name } = member;
|
|
648
|
-
if (!name || !ts3.isIdentifier(name) && !ts3.isStringLiteral(name))
|
|
649
|
-
return null;
|
|
650
|
-
return name.text;
|
|
651
|
-
}
|
|
385
|
+
return ts.isIdentifier(current) ? current.text : "";
|
|
652
386
|
}
|
|
653
387
|
|
|
654
388
|
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
@@ -705,10 +439,7 @@ var RULE_FIXES = {
|
|
|
705
439
|
"akan.ssr.client-static-markup": "Keep the interactive element in the client component and hoist the static subtree into a server component, then accept it as `children` or render it through a Unit/View reference.",
|
|
706
440
|
"akan.ssr.client-mount-load": "Load the data in the route with `fetch.initX(...)` / `fetch.viewX(...)` and pass the init/view object down as a prop; the client store hydrates from it and the effect goes away.",
|
|
707
441
|
"akan.ssr.module-missing-server-view": "Add a <Model>.Unit.tsx for list/card rendering and a <Model>.View.tsx for the detail surface, then have the Zone delegate to them.",
|
|
708
|
-
"akan.ssr.template-client-state": "Bind the field to the store instead: `value={xForm.field}` with `onChange={st.do.setFieldOnX}`."
|
|
709
|
-
"akan.mcp.missing-description": "Add `.desc([en, ko])` to this entry in the module's dictionary \u2014 for a slice, either on the slice entry or on the `<model>List<Slice>` endpoint it generates. Describe when to reach for it, not what it is named.",
|
|
710
|
-
"akan.agent.missing-store-description": "Add the action to the module dictionary's `.store()` stage with a `.desc([en, ko])` saying what it does for the user \u2014 not what the endpoint it calls does. That stage is optional everywhere else: an action named after its endpoint already reads as that endpoint's description.",
|
|
711
|
-
"akan.mcp.unguarded-exposure": "Name the guards in the same option object as `mcp`: `init({ guards: [SignedIn], mcp: { expose: true } })`. Write `guards: [Public]` if anonymous reads are the intent \u2014 the access is the same, but only one of the two is a decision. The `slice()` call's guards map reaches the root slice and base CRUD, never a named slice."
|
|
442
|
+
"akan.ssr.template-client-state": "Bind the field to the store instead: `value={xForm.field}` with `onChange={st.do.setFieldOnX}`."
|
|
712
443
|
};
|
|
713
444
|
function getRuleFix(rule) {
|
|
714
445
|
if (rule.startsWith("akan.convention"))
|
|
@@ -729,9 +460,7 @@ class AkanQualityScanner {
|
|
|
729
460
|
...sourceFiles.flatMap((sourceFile) => this.#scanConventionQuality(sourceFile)),
|
|
730
461
|
...sourceFiles.flatMap((sourceFile) => this.#scanLayoutQuality(sourceFile)),
|
|
731
462
|
...abstractFiles.flatMap((abstractFile) => this.#scanAbstractQuality(abstractFile)),
|
|
732
|
-
...ssr.warnings
|
|
733
|
-
...new McpScanner().scan(sourceFiles),
|
|
734
|
-
...new StoreScanner().scan(sourceFiles)
|
|
463
|
+
...ssr.warnings
|
|
735
464
|
];
|
|
736
465
|
return {
|
|
737
466
|
workspaceRoot,
|
|
@@ -745,7 +474,7 @@ class AkanQualityScanner {
|
|
|
745
474
|
const ignoreFilter = ignore().add(await this.#readGitIgnore(workspaceRoot));
|
|
746
475
|
const files = [];
|
|
747
476
|
for (const targetRoot of ["apps", "libs"]) {
|
|
748
|
-
const absoluteTargetRoot =
|
|
477
|
+
const absoluteTargetRoot = path.join(workspaceRoot, targetRoot);
|
|
749
478
|
if (!await isDirectory(absoluteTargetRoot))
|
|
750
479
|
continue;
|
|
751
480
|
await this.#walkTargetFiles(workspaceRoot, absoluteTargetRoot, ignoreFilter, files);
|
|
@@ -753,7 +482,7 @@ class AkanQualityScanner {
|
|
|
753
482
|
return files.sort();
|
|
754
483
|
}
|
|
755
484
|
async#readGitIgnore(workspaceRoot) {
|
|
756
|
-
const gitIgnorePath =
|
|
485
|
+
const gitIgnorePath = path.join(workspaceRoot, ".gitignore");
|
|
757
486
|
if (!await Bun.file(gitIgnorePath).exists())
|
|
758
487
|
return [];
|
|
759
488
|
return (await readFile(gitIgnorePath, "utf8")).split(/\r?\n/);
|
|
@@ -761,8 +490,8 @@ class AkanQualityScanner {
|
|
|
761
490
|
async#walkTargetFiles(workspaceRoot, currentPath, ignoreFilter, files) {
|
|
762
491
|
const entries = await readdir(currentPath, { withFileTypes: true });
|
|
763
492
|
for (const entry of entries) {
|
|
764
|
-
const absolutePath =
|
|
765
|
-
const relativePath = toPosix(
|
|
493
|
+
const absolutePath = path.join(currentPath, entry.name);
|
|
494
|
+
const relativePath = toPosix(path.relative(workspaceRoot, absolutePath));
|
|
766
495
|
if (shouldSkipPath(relativePath, entry.isDirectory(), ignoreFilter))
|
|
767
496
|
continue;
|
|
768
497
|
if (entry.isDirectory()) {
|
|
@@ -777,17 +506,17 @@ class AkanQualityScanner {
|
|
|
777
506
|
}
|
|
778
507
|
}
|
|
779
508
|
async#readSourceFile(workspaceRoot, file) {
|
|
780
|
-
const absolutePath =
|
|
509
|
+
const absolutePath = path.join(workspaceRoot, file);
|
|
781
510
|
const content = await readFile(absolutePath, "utf8");
|
|
782
511
|
return {
|
|
783
512
|
file,
|
|
784
513
|
absolutePath,
|
|
785
514
|
content,
|
|
786
|
-
sourceFile:
|
|
515
|
+
sourceFile: ts2.createSourceFile(file, content, ts2.ScriptTarget.Latest, true, getScriptKind(file))
|
|
787
516
|
};
|
|
788
517
|
}
|
|
789
518
|
async#readTextFile(workspaceRoot, file) {
|
|
790
|
-
return { file, content: await readFile(
|
|
519
|
+
return { file, content: await readFile(path.join(workspaceRoot, file), "utf8") };
|
|
791
520
|
}
|
|
792
521
|
#scanGlobalQuality(sourceFiles) {
|
|
793
522
|
const exportedFunctionLikes = sourceFiles.flatMap((sourceFile) => getExportedFunctionLikes(sourceFile));
|
|
@@ -911,7 +640,7 @@ class AkanQualityScanner {
|
|
|
911
640
|
const suffix = CONVENTION_SUFFIXES.find((candidate) => sourceFile.file.endsWith(candidate));
|
|
912
641
|
if (!suffix)
|
|
913
642
|
return [];
|
|
914
|
-
const modelName = toPascalCase(
|
|
643
|
+
const modelName = toPascalCase(path.basename(sourceFile.file, suffix));
|
|
915
644
|
const warnings = [];
|
|
916
645
|
for (const declaration of getTopLevelDeclarations(sourceFile)) {
|
|
917
646
|
if (isAllowedConventionDeclaration(suffix, modelName, declaration))
|
|
@@ -922,7 +651,7 @@ class AkanQualityScanner {
|
|
|
922
651
|
severity: "warning",
|
|
923
652
|
file: sourceFile.file,
|
|
924
653
|
line: declaration.line,
|
|
925
|
-
message: `${
|
|
654
|
+
message: `${path.basename(sourceFile.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`
|
|
926
655
|
});
|
|
927
656
|
}
|
|
928
657
|
return warnings;
|
|
@@ -1030,7 +759,7 @@ function getExportedFunctionLikes(sourceFile) {
|
|
|
1030
759
|
const declarations = [];
|
|
1031
760
|
const nameExempt = isPageRouteFile(sourceFile.file) || isUiComponentFile(sourceFile.file);
|
|
1032
761
|
for (const statement of sourceFile.sourceFile.statements) {
|
|
1033
|
-
if (
|
|
762
|
+
if (ts2.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
|
|
1034
763
|
declarations.push({
|
|
1035
764
|
name: statement.name.text,
|
|
1036
765
|
kind: "function",
|
|
@@ -1040,7 +769,7 @@ function getExportedFunctionLikes(sourceFile) {
|
|
|
1040
769
|
duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, false)
|
|
1041
770
|
});
|
|
1042
771
|
}
|
|
1043
|
-
if (
|
|
772
|
+
if (ts2.isClassDeclaration(statement) && statement.name && isExported(statement)) {
|
|
1044
773
|
declarations.push({
|
|
1045
774
|
name: statement.name.text,
|
|
1046
775
|
kind: "class",
|
|
@@ -1050,9 +779,9 @@ function getExportedFunctionLikes(sourceFile) {
|
|
|
1050
779
|
duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, isEnumClassStatement(sourceFile.sourceFile, statement))
|
|
1051
780
|
});
|
|
1052
781
|
}
|
|
1053
|
-
if (
|
|
782
|
+
if (ts2.isVariableStatement(statement) && isExported(statement)) {
|
|
1054
783
|
for (const declaration of statement.declarationList.declarations) {
|
|
1055
|
-
if (!
|
|
784
|
+
if (!ts2.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
|
|
1056
785
|
continue;
|
|
1057
786
|
declarations.push({
|
|
1058
787
|
name: declaration.name.text,
|
|
@@ -1091,14 +820,14 @@ function isInLibModule(file) {
|
|
|
1091
820
|
return (segments[0] === "apps" || segments[0] === "libs") && segments.includes("lib");
|
|
1092
821
|
}
|
|
1093
822
|
function isEnumClassStatement(sourceFile, statement) {
|
|
1094
|
-
if (!
|
|
823
|
+
if (!ts2.isClassDeclaration(statement))
|
|
1095
824
|
return false;
|
|
1096
|
-
const heritageClause = statement.heritageClauses?.find((clause) => clause.token ===
|
|
825
|
+
const heritageClause = statement.heritageClauses?.find((clause) => clause.token === ts2.SyntaxKind.ExtendsKeyword);
|
|
1097
826
|
const expression = heritageClause?.types[0]?.expression;
|
|
1098
827
|
return !!expression && expression.getText(sourceFile).startsWith("enumOf(");
|
|
1099
828
|
}
|
|
1100
829
|
function getExportedClassNames(sourceFile) {
|
|
1101
|
-
return sourceFile.statements.filter((statement) =>
|
|
830
|
+
return sourceFile.statements.filter((statement) => ts2.isClassDeclaration(statement) && !!statement.name).filter((statement) => isExported(statement)).map((statement) => statement.name.text);
|
|
1102
831
|
}
|
|
1103
832
|
function isComponentDeclarationFile(file) {
|
|
1104
833
|
if (!file.endsWith(".tsx"))
|
|
@@ -1114,7 +843,7 @@ function isComponentDeclarationFile(file) {
|
|
|
1114
843
|
function getComponentFileDeclarations(sourceFile) {
|
|
1115
844
|
const reExportedNames = new Set;
|
|
1116
845
|
for (const statement of sourceFile.statements) {
|
|
1117
|
-
if (
|
|
846
|
+
if (ts2.isExportDeclaration(statement) && statement.exportClause && ts2.isNamedExports(statement.exportClause)) {
|
|
1118
847
|
for (const element of statement.exportClause.elements)
|
|
1119
848
|
reExportedNames.add((element.propertyName ?? element.name).text);
|
|
1120
849
|
}
|
|
@@ -1124,19 +853,19 @@ function getComponentFileDeclarations(sourceFile) {
|
|
|
1124
853
|
const isDefaultExport = isDefaultExportStatement(statement);
|
|
1125
854
|
const inlineExported = isExported(statement);
|
|
1126
855
|
const add = (name, kind, line) => declarations.push({ name, kind, line, exported: inlineExported || reExportedNames.has(name), isDefaultExport });
|
|
1127
|
-
if (
|
|
856
|
+
if (ts2.isInterfaceDeclaration(statement))
|
|
1128
857
|
add(statement.name.text, "interface", getLine(sourceFile, statement));
|
|
1129
|
-
else if (
|
|
858
|
+
else if (ts2.isTypeAliasDeclaration(statement))
|
|
1130
859
|
add(statement.name.text, "type", getLine(sourceFile, statement));
|
|
1131
|
-
else if (
|
|
860
|
+
else if (ts2.isEnumDeclaration(statement))
|
|
1132
861
|
add(statement.name.text, "enum", getLine(sourceFile, statement));
|
|
1133
|
-
else if (
|
|
862
|
+
else if (ts2.isFunctionDeclaration(statement) && statement.name)
|
|
1134
863
|
add(statement.name.text, "function", getLine(sourceFile, statement));
|
|
1135
|
-
else if (
|
|
864
|
+
else if (ts2.isClassDeclaration(statement) && statement.name)
|
|
1136
865
|
add(statement.name.text, "class", getLine(sourceFile, statement));
|
|
1137
|
-
else if (
|
|
866
|
+
else if (ts2.isVariableStatement(statement)) {
|
|
1138
867
|
for (const declaration of statement.declarationList.declarations) {
|
|
1139
|
-
if (!
|
|
868
|
+
if (!ts2.isIdentifier(declaration.name))
|
|
1140
869
|
continue;
|
|
1141
870
|
const kind = isFunctionLikeInitializer(declaration.initializer) ? "function" : "variable";
|
|
1142
871
|
add(declaration.name.text, kind, getLine(sourceFile, declaration));
|
|
@@ -1148,15 +877,15 @@ function getComponentFileDeclarations(sourceFile) {
|
|
|
1148
877
|
function getCompoundComponentNames(sourceFile) {
|
|
1149
878
|
const names = new Set;
|
|
1150
879
|
for (const statement of sourceFile.statements) {
|
|
1151
|
-
if (!
|
|
880
|
+
if (!ts2.isExpressionStatement(statement))
|
|
1152
881
|
continue;
|
|
1153
882
|
const { expression } = statement;
|
|
1154
|
-
if (!
|
|
883
|
+
if (!ts2.isBinaryExpression(expression) || expression.operatorToken.kind !== ts2.SyntaxKind.EqualsToken)
|
|
1155
884
|
continue;
|
|
1156
|
-
if (!
|
|
885
|
+
if (!ts2.isPropertyAccessExpression(expression.left) || !isPascalCaseName(expression.left.name.text))
|
|
1157
886
|
continue;
|
|
1158
887
|
names.add(expression.left.name.text);
|
|
1159
|
-
if (
|
|
888
|
+
if (ts2.isIdentifier(expression.right) && isPascalCaseName(expression.right.text))
|
|
1160
889
|
names.add(expression.right.text);
|
|
1161
890
|
}
|
|
1162
891
|
return names;
|
|
@@ -1176,9 +905,9 @@ function isPascalCaseName(name) {
|
|
|
1176
905
|
return /^[A-Z]/.test(name) && !/^[A-Z0-9_]+$/.test(name);
|
|
1177
906
|
}
|
|
1178
907
|
function isDefaultExportStatement(statement) {
|
|
1179
|
-
if (
|
|
908
|
+
if (ts2.isExportAssignment(statement))
|
|
1180
909
|
return !statement.isExportEquals;
|
|
1181
|
-
return !!(
|
|
910
|
+
return !!(ts2.getCombinedModifierFlags(statement) & ts2.ModifierFlags.Default);
|
|
1182
911
|
}
|
|
1183
912
|
function getPlaceholderExportWarnings(sourceFile) {
|
|
1184
913
|
if (!sourceFile.file.endsWith("/index.ts") && !sourceFile.file.endsWith("/index.tsx"))
|
|
@@ -1208,7 +937,7 @@ function getDictionaryTextWarnings(sourceFile) {
|
|
|
1208
937
|
function getGlobalMutationWarnings(sourceFile) {
|
|
1209
938
|
const warnings = [];
|
|
1210
939
|
for (const statement of sourceFile.sourceFile.statements) {
|
|
1211
|
-
if (
|
|
940
|
+
if (ts2.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global") {
|
|
1212
941
|
warnings.push({
|
|
1213
942
|
rule: "akan.file.global-declaration",
|
|
1214
943
|
scope: "file",
|
|
@@ -1218,7 +947,7 @@ function getGlobalMutationWarnings(sourceFile) {
|
|
|
1218
947
|
message: "Global declarations require an explicit low-level integration allowlist."
|
|
1219
948
|
});
|
|
1220
949
|
}
|
|
1221
|
-
if (
|
|
950
|
+
if (ts2.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
|
|
1222
951
|
warnings.push({
|
|
1223
952
|
rule: "akan.file.window-augmentation",
|
|
1224
953
|
scope: "file",
|
|
@@ -1228,7 +957,7 @@ function getGlobalMutationWarnings(sourceFile) {
|
|
|
1228
957
|
message: "Window augmentation should be isolated to approved browser integration files."
|
|
1229
958
|
});
|
|
1230
959
|
}
|
|
1231
|
-
if (
|
|
960
|
+
if (ts2.isExpressionStatement(statement) && statement.expression.getText(sourceFile.sourceFile).includes(".prototype.")) {
|
|
1232
961
|
warnings.push({
|
|
1233
962
|
rule: "akan.file.prototype-mutation",
|
|
1234
963
|
scope: "file",
|
|
@@ -1246,23 +975,23 @@ function getTopLevelDeclarations(sourceFile) {
|
|
|
1246
975
|
}
|
|
1247
976
|
function getTopLevelDeclaration(sourceFile, statement) {
|
|
1248
977
|
const line = getLine(sourceFile, statement);
|
|
1249
|
-
if (
|
|
978
|
+
if (ts2.isClassDeclaration(statement) && statement.name) {
|
|
1250
979
|
return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
|
|
1251
980
|
}
|
|
1252
|
-
if (
|
|
981
|
+
if (ts2.isFunctionDeclaration(statement) && statement.name) {
|
|
1253
982
|
return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
|
|
1254
983
|
}
|
|
1255
|
-
if (
|
|
984
|
+
if (ts2.isInterfaceDeclaration(statement)) {
|
|
1256
985
|
return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
|
|
1257
986
|
}
|
|
1258
|
-
if (
|
|
987
|
+
if (ts2.isTypeAliasDeclaration(statement)) {
|
|
1259
988
|
return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
|
|
1260
989
|
}
|
|
1261
|
-
if (
|
|
990
|
+
if (ts2.isEnumDeclaration(statement)) {
|
|
1262
991
|
return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
|
|
1263
992
|
}
|
|
1264
|
-
if (
|
|
1265
|
-
return statement.declarationList.declarations.filter((declaration) =>
|
|
993
|
+
if (ts2.isVariableStatement(statement)) {
|
|
994
|
+
return statement.declarationList.declarations.filter((declaration) => ts2.isIdentifier(declaration.name)).map((declaration) => ({
|
|
1266
995
|
name: declaration.name.text,
|
|
1267
996
|
kind: "variable",
|
|
1268
997
|
line: getLine(sourceFile, declaration),
|
|
@@ -1270,7 +999,7 @@ function getTopLevelDeclaration(sourceFile, statement) {
|
|
|
1270
999
|
node: statement
|
|
1271
1000
|
}));
|
|
1272
1001
|
}
|
|
1273
|
-
if (
|
|
1002
|
+
if (ts2.isExportDeclaration(statement)) {
|
|
1274
1003
|
return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
|
|
1275
1004
|
}
|
|
1276
1005
|
return [];
|
|
@@ -1295,9 +1024,9 @@ function isAllowedConstantDeclaration(modelName, declaration) {
|
|
|
1295
1024
|
return false;
|
|
1296
1025
|
if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
|
|
1297
1026
|
return true;
|
|
1298
|
-
if (!
|
|
1027
|
+
if (!ts2.isClassDeclaration(declaration.node))
|
|
1299
1028
|
return false;
|
|
1300
|
-
const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token ===
|
|
1029
|
+
const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts2.SyntaxKind.ExtendsKeyword);
|
|
1301
1030
|
const expression = heritageClause?.types[0]?.expression;
|
|
1302
1031
|
return !!expression && expression.getText().startsWith("enumOf(");
|
|
1303
1032
|
}
|
|
@@ -1363,13 +1092,13 @@ function getModuleInfo(file) {
|
|
|
1363
1092
|
return { moduleName, fileName, kind: "database" };
|
|
1364
1093
|
}
|
|
1365
1094
|
function isExportedConst(declaration) {
|
|
1366
|
-
return declaration.exported &&
|
|
1095
|
+
return declaration.exported && ts2.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts2.NodeFlags.Const) !== 0;
|
|
1367
1096
|
}
|
|
1368
1097
|
function isExported(node) {
|
|
1369
|
-
return !!
|
|
1098
|
+
return !!ts2.getCombinedModifierFlags(node) && !!(ts2.getCombinedModifierFlags(node) & ts2.ModifierFlags.Export);
|
|
1370
1099
|
}
|
|
1371
1100
|
function isFunctionLikeInitializer(node) {
|
|
1372
|
-
return !!node && (
|
|
1101
|
+
return !!node && (ts2.isArrowFunction(node) || ts2.isFunctionExpression(node));
|
|
1373
1102
|
}
|
|
1374
1103
|
function getBodyFingerprint(sourceFile, node) {
|
|
1375
1104
|
if (!node)
|
|
@@ -1391,7 +1120,7 @@ async function isDirectory(absolutePath) {
|
|
|
1391
1120
|
}
|
|
1392
1121
|
}
|
|
1393
1122
|
function getScriptKind(file) {
|
|
1394
|
-
return file.endsWith(".tsx") ?
|
|
1123
|
+
return file.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS;
|
|
1395
1124
|
}
|
|
1396
1125
|
function getLine(sourceFile, node) {
|
|
1397
1126
|
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
@@ -1412,7 +1141,7 @@ function toPascalCase(value) {
|
|
|
1412
1141
|
return value.replace(/(^|[-_./])([a-zA-Z0-9])/g, (_, __, char) => char.toUpperCase()).replace(/[-_./]/g, "");
|
|
1413
1142
|
}
|
|
1414
1143
|
function toPosix(value) {
|
|
1415
|
-
return value.split(
|
|
1144
|
+
return value.split(path.sep).join("/");
|
|
1416
1145
|
}
|
|
1417
1146
|
function groupBy(items, getKey) {
|
|
1418
1147
|
const grouped = new Map;
|