@akanjs/devkit 3.0.0-beta.9 → 3.0.1
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/CHANGELOG.md +1819 -0
- package/DEV_RUNTIME_KNOBS.md +44 -26
- package/README.ko.md +21 -7
- package/README.md +17 -8
- package/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +6 -8
- package/agentsIndex.test.ts +4 -7
- package/agentsIndex.ts +6 -22
- package/akanApp/BackendImportGraph.test.ts +8 -22
- package/akanApp/BackendImportGraph.ts +1 -20
- package/akanApp/akanApp.host.test.ts +56 -3
- package/akanApp/akanApp.host.ts +218 -278
- package/akanApp/devBootLatch.test.ts +39 -0
- package/akanApp/devBootLatch.ts +39 -0
- package/akanApp/devHostPolicy.test.ts +81 -35
- package/akanApp/devHostPolicy.ts +41 -102
- package/akanConfig/akanBin.test.ts +62 -0
- package/akanConfig/akanBin.ts +78 -0
- package/akanConfig/akanConfig.test.ts +439 -141
- package/akanConfig/akanConfig.ts +359 -275
- package/akanConfig/index.ts +1 -0
- package/akanConfig/types.ts +22 -8
- package/akanContext.ts +110 -160
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.test.ts +41 -5
- package/applicationBuildRunner.ts +71 -63
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +34 -31
- package/artifact/implicitRootLayout.ts +60 -107
- package/artifact/routeSeedIndex.test.ts +5 -18
- package/artifact/routeSeedIndex.ts +11 -44
- package/biome.base.json +39 -2
- package/biomeBase.ts +2 -5
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +77 -39
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +26 -5
- package/cloud/globalConfig.ts +71 -32
- package/codeAgent/agent/AkanCodeServices.ts +97 -0
- package/codeAgent/agent/AkanContextFiles.ts +94 -0
- package/codeAgent/agent/CodeAgent.ts +551 -0
- package/codeAgent/agent/CodeAgentAsks.ts +66 -0
- package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
- package/codeAgent/agent/CodeAgentGate.ts +93 -0
- package/codeAgent/agent/CodeAgentProxy.ts +82 -0
- package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
- package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
- package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
- package/codeAgent/agent/CodeAgentUi.ts +47 -0
- package/codeAgent/agent/CodeMailbox.ts +148 -0
- package/codeAgent/agent/CodeSessionFork.ts +56 -0
- package/codeAgent/agent/CodeSessionIndex.ts +78 -0
- package/codeAgent/agent/SubagentPool.ts +214 -0
- package/codeAgent/agent/akanCodeModel.ts +47 -0
- package/codeAgent/agent/akanCodePaths.ts +31 -0
- package/codeAgent/agent/akanContextFiles.test.ts +114 -0
- package/codeAgent/agent/akanSystemPrompt.ts +46 -0
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +523 -0
- package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
- package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
- package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
- package/codeAgent/agent/codeMailbox.test.ts +76 -0
- package/codeAgent/agent/codeSessionFork.test.ts +58 -0
- package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
- package/codeAgent/agent/consoleToStderr.ts +8 -0
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +82 -0
- package/codeAgent/feedback/PreviewChrome.ts +82 -0
- package/codeAgent/feedback/PreviewFeedback.ts +46 -0
- package/codeAgent/feedback/PreviewView.ts +95 -0
- package/codeAgent/feedback/TurnFeedback.ts +98 -0
- package/codeAgent/feedback/VerifyFeedback.ts +40 -0
- package/codeAgent/feedback/previewChrome.test.ts +73 -0
- package/codeAgent/index.ts +34 -0
- package/codeAgent/skills/akan-code/SKILL.md +130 -0
- package/codeAgent/skills/akan-debug/SKILL.md +42 -0
- package/codeAgent/skills/akan-deps/SKILL.md +50 -0
- package/codeAgent/skills/akan-err/SKILL.md +39 -0
- package/codeAgent/skills/akan-module/SKILL.md +45 -0
- package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
- package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
- package/codeAgent/skills/akan-service/SKILL.md +34 -0
- package/codeAgent/skills/akan-store/SKILL.md +67 -0
- package/codeAgent/skills/akan-ui/SKILL.md +63 -0
- package/codeAgent/skills/akan-validate/SKILL.md +44 -0
- package/codeAgent/tools/AkanCli.ts +50 -0
- package/codeAgent/tools/AkanCodePlugins.ts +136 -0
- package/codeAgent/tools/AkanEditScope.ts +48 -0
- package/codeAgent/tools/AkanEnvKeys.ts +50 -0
- package/codeAgent/tools/AkanToolPack.ts +92 -0
- package/codeAgent/tools/AkanVerifier.ts +48 -0
- package/codeAgent/tools/AskToolPack.ts +99 -0
- package/codeAgent/tools/MailToolPack.ts +76 -0
- package/codeAgent/tools/McpClient.ts +187 -0
- package/codeAgent/tools/McpOAuth.ts +233 -0
- package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
- package/codeAgent/tools/McpServerConfig.ts +129 -0
- package/codeAgent/tools/McpSignIn.ts +158 -0
- package/codeAgent/tools/McpTokenStore.ts +81 -0
- package/codeAgent/tools/McpToolPack.ts +199 -0
- package/codeAgent/tools/SessionToolPack.ts +91 -0
- package/codeAgent/tools/WebToolPack.ts +147 -0
- package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
- package/codeAgent/tools/askToolPack.test.ts +116 -0
- package/codeAgent/tools/mcpOAuth.test.ts +612 -0
- package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
- package/codegenLock.test.ts +4 -13
- package/codegenLock.ts +3 -17
- package/commandDecorators/argMeta.ts +1 -5
- package/commandDecorators/camelToKebabCase.ts +1 -0
- package/commandDecorators/command.ts +46 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +2 -10
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +68 -145
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/csrE2e/csrE2eHarness.fixture.ts +506 -0
- package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
- package/csrE2e/csrPageStack.e2e.test.ts +265 -0
- package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
- package/csrE2e/csrRootLayout.e2e.test.ts +134 -0
- package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
- package/csrE2e/registryVendor.e2e.test.ts +221 -0
- package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +31 -80
- package/devkitUtils.test.ts +48 -20
- package/executors.linkFallback.test.ts +103 -0
- package/executors.test.ts +176 -106
- package/executors.ts +212 -498
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +5 -3
- package/fileSys.ts +22 -9
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +27 -38
- package/frontendBuild/autoImportSync.ts +31 -65
- package/frontendBuild/buildRouteClient.test.ts +123 -37
- package/frontendBuild/bundleDefine.test.ts +12 -0
- package/frontendBuild/bundleDefine.ts +16 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
- package/frontendBuild/clientEntryDiscovery.ts +122 -110
- package/frontendBuild/csrArtifactBuilder.ts +9 -31
- package/frontendBuild/csrDevArtifactWriter.test.ts +151 -0
- package/frontendBuild/csrDevArtifactWriter.ts +307 -0
- package/frontendBuild/csrDevBundler.test.ts +66 -0
- package/frontendBuild/csrDevBundler.ts +95 -0
- package/frontendBuild/csrDevFactoryCheck.test.ts +33 -0
- package/frontendBuild/csrDevFactoryCheck.ts +39 -0
- package/frontendBuild/csrDevModuleCompiler.test.ts +293 -0
- package/frontendBuild/csrDevModuleCompiler.ts +422 -0
- package/frontendBuild/csrDevPatcher.test.ts +572 -0
- package/frontendBuild/csrDevPatcher.ts +548 -0
- package/frontendBuild/csrDevPaths.ts +145 -0
- package/frontendBuild/csrDevResolver.test.ts +239 -0
- package/frontendBuild/csrDevResolver.ts +363 -0
- package/frontendBuild/csrDevTypes.ts +61 -0
- package/frontendBuild/csrEntryFiles.ts +24 -0
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +31 -82
- package/frontendBuild/cssImportResolver.ts +6 -19
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/devRegistryBundler.ts +192 -0
- package/frontendBuild/fontOptimizer.test.ts +6 -14
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +57 -70
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +59 -32
- package/frontendBuild/hmrWatcher.ts +37 -74
- package/frontendBuild/index.ts +4 -0
- package/frontendBuild/pagesBundleBuilder.ts +28 -30
- package/frontendBuild/pagesEntrySourceGenerator.ts +56 -6
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
- package/frontendBuild/registryMetadataFingerprint.ts +28 -0
- package/frontendBuild/routeClientBuilder.ts +100 -62
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/serverGraphFile.test.ts +77 -0
- package/frontendBuild/serverGraphFile.ts +93 -0
- package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +19 -13
- package/frontendBuild/ssrDevBundler.ts +85 -0
- package/frontendBuild/styleContract.ts +1 -8
- package/frontendBuild/themeValidator.ts +4 -26
- package/frontendBuild/watchRootResolver.test.ts +23 -42
- package/frontendBuild/watchRootResolver.ts +5 -27
- package/getRelatedCnsts.ts +12 -92
- package/incrementalBuilder/buildBatch.proc.ts +147 -63
- package/incrementalBuilder/buildBatchProtocol.ts +13 -38
- package/incrementalBuilder/buildBatchRunner.test.ts +65 -0
- package/incrementalBuilder/buildBatchRunner.ts +26 -38
- package/incrementalBuilder/builderChannel.test.ts +24 -49
- package/incrementalBuilder/builderChannel.ts +6 -37
- package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
- package/incrementalBuilder/builderRequestRouter.ts +5 -24
- package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
- package/incrementalBuilder/builderWorkQueue.ts +126 -0
- package/incrementalBuilder/devWatchBatch.test.ts +21 -24
- package/incrementalBuilder/incrementalBuilder.host.test.ts +53 -56
- package/incrementalBuilder/incrementalBuilder.host.ts +68 -81
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +270 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +596 -208
- package/index.ts +5 -19
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +111 -202
- package/integration/devStabilityHarness.test.ts +13 -28
- package/integration/devStabilityHarness.ts +67 -330
- package/integration/ssrMemoryProbe.ts +41 -167
- package/libSource.test.ts +4 -15
- package/libSource.ts +19 -31
- package/lint/__fixtures__/no-document-cookie/bad.tsx +4 -0
- package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
- package/lint/__fixtures__/no-static-in-object-light-model/bad.ts +43 -0
- package/lint/__fixtures__/no-static-in-object-light-model/fixture.json +1 -0
- package/lint/__fixtures__/no-static-in-object-light-model/good.ts +42 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
- package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
- package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
- package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
- package/lint/gritRules.test.ts +8 -24
- package/lint/no-deep-internal-import.grit +2 -2
- package/lint/no-document-cookie.grit +29 -0
- package/lint/no-redeclare-predefined-endpoint.grit +2 -1
- package/lint/no-static-in-object-light-model.grit +27 -0
- package/lint/no-web-only-api-outside-webkit.grit +44 -0
- package/lint/no-web-storage.grit +30 -0
- package/linter.test.ts +1 -2
- package/linter.ts +26 -116
- package/mobile/desktopBin.test.ts +182 -0
- package/mobile/desktopBin.ts +144 -0
- package/mobile/desktopServerStage.test.ts +252 -0
- package/mobile/desktopServerStage.ts +178 -0
- package/mobile/index.ts +9 -0
- package/mobile/mobileTarget.test.ts +57 -8
- package/mobile/mobileTarget.ts +45 -26
- package/mobile/nativeApi.test.ts +30 -0
- package/mobile/nativeApi.ts +40 -0
- package/mobile/nativeApp.test.ts +184 -0
- package/mobile/nativeApp.ts +454 -0
- package/mobile/nativeAppLine.test.ts +69 -0
- package/mobile/nativeAppLine.ts +71 -0
- package/mobile/nativeConfig.test.ts +438 -0
- package/mobile/nativeConfig.ts +294 -0
- package/mobile/nativePluginFolders.test.ts +81 -0
- package/mobile/nativePluginFolders.ts +60 -0
- package/mobile/nativeWebDir.test.ts +80 -0
- package/mobile/nativeWebDir.ts +66 -0
- package/mobile/placeholderAppId.test.ts +13 -0
- package/mobile/placeholderAppId.ts +16 -0
- package/mobile/usageDescriptions.test.ts +27 -0
- package/mobile/usageDescriptions.ts +24 -0
- package/package.json +11 -11
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +46 -0
- package/platformTest/BunTestReport.test.ts +60 -0
- package/platformTest/BunTestReport.ts +65 -0
- package/platformTest/IdleWatchedProcess.test.ts +56 -0
- package/platformTest/IdleWatchedProcess.ts +73 -0
- package/platformTest/LinuxDockerTarget.ts +105 -0
- package/platformTest/PlatformTestRun.test.ts +64 -0
- package/platformTest/PlatformTestRun.ts +141 -0
- package/platformTest/PlatformTestTarget.ts +142 -0
- package/platformTest/TestSnapshot.test.ts +72 -0
- package/platformTest/TestSnapshot.ts +103 -0
- package/platformTest/WindowsSshTarget.ts +151 -0
- package/prompter.ts +3 -13
- package/qualityScanner.test.ts +9 -26
- package/qualityScanner.ts +200 -286
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.test.ts +0 -1
- package/routeSourceValidator.ts +7 -42
- package/scanInfo.ts +91 -225
- package/selectModel.ts +1 -2
- package/semver.ts +3 -5
- package/slicePlanner.test.ts +6 -24
- package/slicePlanner.ts +11 -32
- package/spinner.test.ts +3 -8
- package/spinner.ts +4 -16
- package/sshTunnel.ts +1 -6
- package/ssrScanner.test.ts +1 -6
- package/ssrScanner.ts +103 -113
- package/stripAnsi.ts +3 -0
- package/subspace.test.ts +19 -26
- package/subspace.ts +48 -115
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +194 -0
- package/transforms/asyncDefaultExportDetector.ts +3 -11
- package/transforms/barrelAnalyzer.ts +43 -135
- package/transforms/barrelImportsPlugin.ts +38 -175
- package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
- package/transforms/externalizeFrameworkPlugin.ts +13 -87
- package/transforms/moduleSyntax.ts +32 -0
- package/transforms/rscUseClientTransform.ts +42 -20
- package/transforms/serverEnvPlugin.test.ts +103 -0
- package/transforms/serverEnvPlugin.ts +41 -0
- package/transforms/transforms.test.ts +166 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +6 -23
- package/typeChecker.ts +7 -63
- package/types.ts +2 -0
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +101 -110
- package/workflow/index.ts +0 -1
- package/workflow/moduleIndex.test.ts +4 -15
- package/workflow/moduleIndex.ts +9 -49
- package/workflow/plan.ts +18 -25
- package/workflow/render.ts +80 -96
- package/workflow/rolloutGate.test.ts +1 -1
- package/workflow/source.ts +52 -69
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.test.ts +18 -3
- package/workspaceLayout.ts +22 -23
- package/abstractCompactor.ts +0 -112
- package/aiEditor.test.ts +0 -60
- package/aiEditor.ts +0 -387
- package/applicationReleasePackager.ts +0 -209
- package/capacitorApp.test.ts +0 -372
- package/capacitorApp.ts +0 -1648
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/guideline.ts +0 -19
- package/local/optimize-resource/ipcprobe/child.ts +0 -1
- package/local/optimize-resource/ipcprobe/parent.ts +0 -11
- package/workflow/primitive.ts +0 -59
package/qualityScanner.ts
CHANGED
|
@@ -6,8 +6,8 @@ import ignore from "ignore";
|
|
|
6
6
|
import ts from "typescript";
|
|
7
7
|
import { AbstractDoc } from "./abstractDoc";
|
|
8
8
|
import { FormSetterScanner } from "./formSetterScanner";
|
|
9
|
-
import { formatSsrBalance, type SsrBalanceEntry, SsrScanner } from "./ssrScanner";
|
|
10
|
-
import { isAllowedLibFacetRootFile, rootAllowedDirs, rootAllowedFiles } from "./workspaceLayout";
|
|
9
|
+
import { fileWarning, formatSsrBalance, type SsrBalanceEntry, SsrScanner } from "./ssrScanner";
|
|
10
|
+
import { isAllowedLibFacetRootFile, rootAllowedDirs, rootAllowedFiles, rootEntryHintOf } from "./workspaceLayout";
|
|
11
11
|
|
|
12
12
|
type QualitySeverity = "warning";
|
|
13
13
|
type QualityScope = "global" | "file" | "convention" | "layout" | "ssr" | "agent";
|
|
@@ -103,8 +103,6 @@ const CONVENTION_SUFFIXES = [
|
|
|
103
103
|
".store.ts",
|
|
104
104
|
] as const;
|
|
105
105
|
|
|
106
|
-
// How to remediate each rule, keyed by rule id. Surfaced as a `fix:` line per warning (text + JSON output)
|
|
107
|
-
// so the scan result tells the reader what to do, not just what is wrong.
|
|
108
106
|
const RULE_FIXES: Record<string, string> = {
|
|
109
107
|
"akan.global.duplicate-exported-function-name":
|
|
110
108
|
"Rename one of the exports, or if they are the same thing, extract it into one shared module and import it in both places.",
|
|
@@ -114,7 +112,7 @@ const RULE_FIXES: Record<string, string> = {
|
|
|
114
112
|
"Split the file by responsibility — move Zones, Utils, or subcomponents into sibling files.",
|
|
115
113
|
"akan.file.max-lines": "Break the file into smaller focused modules; keep one primary responsibility per file.",
|
|
116
114
|
"akan.file.abstract-max-lines":
|
|
117
|
-
"
|
|
115
|
+
"Trim the abstract by hand to a title, one sentence naming what the module owns, a `## Rules` list of the invariants the source cannot show, and an optional workflow chain.",
|
|
118
116
|
"akan.file.placeholder-export":
|
|
119
117
|
"Remove the placeholder export; generated indexes should only re-export real modules.",
|
|
120
118
|
"akan.file.bang-comment-in-client":
|
|
@@ -297,24 +295,24 @@ export class AkanQualityScanner {
|
|
|
297
295
|
const warnings: QualityWarning[] = [];
|
|
298
296
|
const lineCount = sourceFile.content.split(/\r?\n/).length;
|
|
299
297
|
const recommendedLineLimit = getRecommendedLineLimit(sourceFile.file);
|
|
300
|
-
if (recommendedLineLimit && lineCount > recommendedLineLimit)
|
|
301
|
-
warnings.push(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (lineCount > MAX_FILE_LINES)
|
|
310
|
-
warnings.push(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
298
|
+
if (recommendedLineLimit && lineCount > recommendedLineLimit)
|
|
299
|
+
warnings.push(
|
|
300
|
+
fileWarning(
|
|
301
|
+
"akan.file.recommended-max-lines",
|
|
302
|
+
"file",
|
|
303
|
+
sourceFile.file,
|
|
304
|
+
`File has ${lineCount} lines. Recommended limit for this file type is ${recommendedLineLimit} lines.`,
|
|
305
|
+
),
|
|
306
|
+
);
|
|
307
|
+
if (lineCount > MAX_FILE_LINES)
|
|
308
|
+
warnings.push(
|
|
309
|
+
fileWarning(
|
|
310
|
+
"akan.file.max-lines",
|
|
311
|
+
"file",
|
|
312
|
+
sourceFile.file,
|
|
313
|
+
`File has ${lineCount} lines. Keep single files under ${MAX_FILE_LINES} lines.`,
|
|
314
|
+
),
|
|
315
|
+
);
|
|
318
316
|
|
|
319
317
|
warnings.push(...getBangCommentWarnings(sourceFile));
|
|
320
318
|
warnings.push(...getPlaceholderExportWarnings(sourceFile));
|
|
@@ -327,14 +325,15 @@ export class AkanQualityScanner {
|
|
|
327
325
|
for (const declaration of getTopLevelDeclarations(sourceFile)) {
|
|
328
326
|
if (declaration.kind === "class" && exportedClassNames.includes(declaration.name)) continue;
|
|
329
327
|
if (declaration.kind === "interface" && allowedInterfaceNames.has(declaration.name)) continue;
|
|
330
|
-
warnings.push(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
328
|
+
warnings.push(
|
|
329
|
+
fileWarning(
|
|
330
|
+
"akan.file.class-export-global-declaration",
|
|
331
|
+
"file",
|
|
332
|
+
sourceFile.file,
|
|
333
|
+
`Class export files should not declare top-level ${declaration.kind} "${declaration.name}". Move helpers to another file and import them.`,
|
|
334
|
+
declaration.line,
|
|
335
|
+
),
|
|
336
|
+
);
|
|
338
337
|
}
|
|
339
338
|
return warnings;
|
|
340
339
|
}
|
|
@@ -343,9 +342,7 @@ export class AkanQualityScanner {
|
|
|
343
342
|
if (!isComponentDeclarationFile(sourceFile.file)) return [];
|
|
344
343
|
const isPage = isPageRouteFile(sourceFile.file);
|
|
345
344
|
const declarations = getComponentFileDeclarations(sourceFile.sourceFile);
|
|
346
|
-
// Compound/namespaced components (e.g. `Like.WithDislike = WithDislike`) are valid without being exported.
|
|
347
345
|
const compoundComponentNames = getCompoundComponentNames(sourceFile.sourceFile);
|
|
348
|
-
// Components are the exported (or compound) PascalCase values; their "<Component>Props" interface may live here.
|
|
349
346
|
const componentNames = declarations
|
|
350
347
|
.filter((declaration) => declaration.exported && isComponentValueKind(declaration.kind))
|
|
351
348
|
.filter((declaration) => isPascalCaseName(declaration.name))
|
|
@@ -358,28 +355,28 @@ export class AkanQualityScanner {
|
|
|
358
355
|
if (declaration.kind === "interface" && allowedPropsInterfaces.has(declaration.name)) continue;
|
|
359
356
|
if (declaration.exported) {
|
|
360
357
|
if (isAllowedComponentExport(declaration, isPage)) continue;
|
|
361
|
-
warnings.push(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
358
|
+
warnings.push(
|
|
359
|
+
fileWarning(
|
|
360
|
+
"akan.file.component-export",
|
|
361
|
+
"file",
|
|
362
|
+
sourceFile.file,
|
|
363
|
+
`Component file exports ${declaration.kind} "${declaration.name}", which is not a PascalCase component${isPage ? " or reserved route export" : ""}.`,
|
|
364
|
+
declaration.line,
|
|
365
|
+
),
|
|
366
|
+
);
|
|
369
367
|
continue;
|
|
370
368
|
}
|
|
371
|
-
// A non-exported PascalCase component attached as a compound member is an accepted pattern.
|
|
372
369
|
if (isComponentValueKind(declaration.kind) && compoundComponentNames.has(declaration.name)) continue;
|
|
373
|
-
if (isRestrictedInternalKind(declaration.kind))
|
|
374
|
-
warnings.push(
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
370
|
+
if (isRestrictedInternalKind(declaration.kind))
|
|
371
|
+
warnings.push(
|
|
372
|
+
fileWarning(
|
|
373
|
+
"akan.file.component-internal-declaration",
|
|
374
|
+
"file",
|
|
375
|
+
sourceFile.file,
|
|
376
|
+
`Component file declares non-exported ${declaration.kind} "${declaration.name}". Only "interface <Component>Props" may stay internal.`,
|
|
377
|
+
declaration.line,
|
|
378
|
+
),
|
|
379
|
+
);
|
|
383
380
|
}
|
|
384
381
|
return warnings;
|
|
385
382
|
}
|
|
@@ -389,35 +386,29 @@ export class AkanQualityScanner {
|
|
|
389
386
|
if (!suffix) return [];
|
|
390
387
|
|
|
391
388
|
const modelName = toPascalCase(path.basename(sourceFile.file, suffix));
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
modelName,
|
|
404
|
-
)}.`,
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
return warnings;
|
|
389
|
+
return getTopLevelDeclarations(sourceFile)
|
|
390
|
+
.filter((declaration) => !isAllowedConventionDeclaration(suffix, modelName, declaration))
|
|
391
|
+
.map((declaration) =>
|
|
392
|
+
fileWarning(
|
|
393
|
+
`akan.convention${suffix.replace(".ts", "")}`,
|
|
394
|
+
"convention",
|
|
395
|
+
sourceFile.file,
|
|
396
|
+
`${path.basename(sourceFile.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`,
|
|
397
|
+
declaration.line,
|
|
398
|
+
),
|
|
399
|
+
);
|
|
408
400
|
}
|
|
409
401
|
|
|
410
402
|
#scanAbstractQuality({ file, content }: TextFileInfo): QualityWarning[] {
|
|
411
403
|
const lineCount = AbstractDoc.lineCountOf(content);
|
|
412
404
|
if (lineCount <= AbstractDoc.maxLines) return [];
|
|
413
405
|
return [
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
severity: "warning",
|
|
406
|
+
fileWarning(
|
|
407
|
+
"akan.file.abstract-max-lines",
|
|
408
|
+
"file",
|
|
418
409
|
file,
|
|
419
|
-
|
|
420
|
-
|
|
410
|
+
`Abstract has ${lineCount} lines. Keep abstracts under ${AbstractDoc.maxLines} lines and compact them periodically.`,
|
|
411
|
+
),
|
|
421
412
|
];
|
|
422
413
|
}
|
|
423
414
|
|
|
@@ -428,27 +419,29 @@ export class AkanQualityScanner {
|
|
|
428
419
|
const { type, name, isDir } = rootEntry;
|
|
429
420
|
const allowed = isDir ? rootAllowedDirs[type].has(name) : rootAllowedFiles[type].has(name);
|
|
430
421
|
const kind = isDir ? "folder" : "file";
|
|
431
|
-
if (!allowed)
|
|
432
|
-
warnings.push(
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
422
|
+
if (!allowed)
|
|
423
|
+
warnings.push(
|
|
424
|
+
fileWarning(
|
|
425
|
+
`akan.layout.${type}-root-${kind}`,
|
|
426
|
+
"layout",
|
|
427
|
+
sourceFile.file,
|
|
428
|
+
`Unexpected ${type} root ${kind} "${name}". ${
|
|
429
|
+
rootEntryHintOf(type, name) ?? `Keep ${type} code in conventional ${type} folders.`
|
|
430
|
+
}`,
|
|
431
|
+
),
|
|
432
|
+
);
|
|
440
433
|
}
|
|
441
434
|
|
|
442
435
|
const libFacetFile = getLibFacetRootFile(sourceFile.file);
|
|
443
|
-
if (libFacetFile && !isAllowedLibFacetRootFile(libFacetFile))
|
|
444
|
-
warnings.push(
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
436
|
+
if (libFacetFile && !isAllowedLibFacetRootFile(libFacetFile))
|
|
437
|
+
warnings.push(
|
|
438
|
+
fileWarning(
|
|
439
|
+
"akan.layout.lib-facet-file",
|
|
440
|
+
"layout",
|
|
441
|
+
sourceFile.file,
|
|
442
|
+
`Unexpected lib facet root file "${libFacetFile}". Keep direct lib/ root files limited to generated support facets.`,
|
|
443
|
+
),
|
|
444
|
+
);
|
|
452
445
|
|
|
453
446
|
const moduleUiWarning = getModuleUiWarning(sourceFile.file);
|
|
454
447
|
if (moduleUiWarning) warnings.push(moduleUiWarning);
|
|
@@ -517,79 +510,59 @@ function formatQualityLocation(file: string | undefined, line: number | undefine
|
|
|
517
510
|
|
|
518
511
|
function getExportedFunctionLikes(sourceFile: SourceFileInfo): ExportedFunctionLike[] {
|
|
519
512
|
const declarations: ExportedFunctionLike[] = [];
|
|
520
|
-
// UI component names (
|
|
521
|
-
// duplicate-exported-name check. This only relaxes the name check; the shared-body check still applies.
|
|
513
|
+
// UI component names (Card, Button) repeat across apps and libs by design; only the name check is relaxed.
|
|
522
514
|
const nameExempt = isPageRouteFile(sourceFile.file) || isUiComponentFile(sourceFile.file);
|
|
515
|
+
const add = (
|
|
516
|
+
name: string,
|
|
517
|
+
kind: ExportedFunctionLike["kind"],
|
|
518
|
+
node: ts.Node,
|
|
519
|
+
body: ts.Node | undefined,
|
|
520
|
+
isEnumClass = false,
|
|
521
|
+
) =>
|
|
522
|
+
declarations.push({
|
|
523
|
+
name,
|
|
524
|
+
kind,
|
|
525
|
+
file: sourceFile.file,
|
|
526
|
+
line: getLine(sourceFile.sourceFile, node),
|
|
527
|
+
bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, body),
|
|
528
|
+
duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, isEnumClass),
|
|
529
|
+
});
|
|
523
530
|
for (const statement of sourceFile.sourceFile.statements) {
|
|
524
|
-
if (ts.isFunctionDeclaration(statement) && statement.name && isExported(statement))
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
file: sourceFile.file,
|
|
529
|
-
line: getLine(sourceFile.sourceFile, statement),
|
|
530
|
-
bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, statement.body),
|
|
531
|
-
duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, false),
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
if (ts.isClassDeclaration(statement) && statement.name && isExported(statement)) {
|
|
535
|
-
declarations.push({
|
|
536
|
-
name: statement.name.text,
|
|
537
|
-
kind: "class",
|
|
538
|
-
file: sourceFile.file,
|
|
539
|
-
line: getLine(sourceFile.sourceFile, statement),
|
|
540
|
-
bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, statement),
|
|
541
|
-
duplicateNameExempt:
|
|
542
|
-
nameExempt ||
|
|
543
|
-
isConventionDuplicateNameExempt(sourceFile.file, isEnumClassStatement(sourceFile.sourceFile, statement)),
|
|
544
|
-
});
|
|
545
|
-
}
|
|
531
|
+
if (ts.isFunctionDeclaration(statement) && statement.name && isExported(statement))
|
|
532
|
+
add(statement.name.text, "function", statement, statement.body);
|
|
533
|
+
if (ts.isClassDeclaration(statement) && statement.name && isExported(statement))
|
|
534
|
+
add(statement.name.text, "class", statement, statement, isEnumClassStatement(sourceFile.sourceFile, statement));
|
|
546
535
|
if (ts.isVariableStatement(statement) && isExported(statement)) {
|
|
547
536
|
for (const declaration of statement.declarationList.declarations) {
|
|
548
537
|
if (!ts.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer)) continue;
|
|
549
|
-
|
|
550
|
-
name: declaration.name.text,
|
|
551
|
-
kind: "function-variable",
|
|
552
|
-
file: sourceFile.file,
|
|
553
|
-
line: getLine(sourceFile.sourceFile, declaration),
|
|
554
|
-
bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, declaration.initializer),
|
|
555
|
-
duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, false),
|
|
556
|
-
});
|
|
538
|
+
add(declaration.name.text, "function-variable", declaration, declaration.initializer);
|
|
557
539
|
}
|
|
558
540
|
}
|
|
559
541
|
}
|
|
560
542
|
return declarations;
|
|
561
543
|
}
|
|
562
544
|
|
|
563
|
-
function
|
|
545
|
+
function sysSegments(file: string) {
|
|
564
546
|
const segments = file.split("/");
|
|
565
|
-
return
|
|
547
|
+
return segments[0] === "apps" || segments[0] === "libs" ? segments : null;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function isPageRouteFile(file: string) {
|
|
551
|
+
return sysSegments(file)?.[2] === "page";
|
|
566
552
|
}
|
|
567
553
|
|
|
568
554
|
function isUiComponentFile(file: string) {
|
|
569
|
-
|
|
570
|
-
return (segments[0] === "apps" || segments[0] === "libs") && segments[2] === "ui";
|
|
555
|
+
return sysSegments(file)?.[2] === "ui";
|
|
571
556
|
}
|
|
572
557
|
|
|
573
558
|
function isConventionDuplicateNameExempt(file: string, isEnumClass: boolean) {
|
|
574
|
-
if (!
|
|
575
|
-
if (file.endsWith(".tsx")) return true;
|
|
576
|
-
if (
|
|
577
|
-
file.endsWith(".document.ts") ||
|
|
578
|
-
file.endsWith(".service.ts") ||
|
|
579
|
-
file.endsWith(".signal.ts") ||
|
|
580
|
-
file.endsWith(".store.ts")
|
|
581
|
-
)
|
|
582
|
-
return true;
|
|
559
|
+
if (!sysSegments(file)?.includes("lib")) return false;
|
|
560
|
+
if (file.endsWith(".tsx") || /\.(document|service|signal|store)\.ts$/.test(file)) return true;
|
|
583
561
|
// Model view classes may repeat across modules; enum classes must stay uniquely named.
|
|
584
562
|
if (file.endsWith(".constant.ts")) return !isEnumClass;
|
|
585
563
|
return false;
|
|
586
564
|
}
|
|
587
565
|
|
|
588
|
-
function isInLibModule(file: string) {
|
|
589
|
-
const segments = file.split("/");
|
|
590
|
-
return (segments[0] === "apps" || segments[0] === "libs") && segments.includes("lib");
|
|
591
|
-
}
|
|
592
|
-
|
|
593
566
|
function isEnumClassStatement(sourceFile: ts.SourceFile, statement: ts.Statement) {
|
|
594
567
|
if (!ts.isClassDeclaration(statement)) return false;
|
|
595
568
|
const heritageClause = statement.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword);
|
|
@@ -646,9 +619,7 @@ function getComponentFileDeclarations(sourceFile: ts.SourceFile): ComponentFileD
|
|
|
646
619
|
return declarations;
|
|
647
620
|
}
|
|
648
621
|
|
|
649
|
-
//
|
|
650
|
-
// member is the public component name; when the right side is a PascalCase identifier it is the local
|
|
651
|
-
// definition. Both are treated as valid components so the definition and its "<Component>Props" may stay local.
|
|
622
|
+
// `Like.WithDislike = WithDislike` makes both names components, so the local definition and its Props may stay unexported.
|
|
652
623
|
function getCompoundComponentNames(sourceFile: ts.SourceFile): Set<string> {
|
|
653
624
|
const names = new Set<string>();
|
|
654
625
|
for (const statement of sourceFile.statements) {
|
|
@@ -676,7 +647,6 @@ function isAllowedComponentExport(declaration: ComponentFileDeclaration, isPage:
|
|
|
676
647
|
}
|
|
677
648
|
|
|
678
649
|
function isPascalCaseName(name: string) {
|
|
679
|
-
// PascalCase component names start uppercase and are not SCREAMING_SNAKE_CASE constants.
|
|
680
650
|
return /^[A-Z]/.test(name) && !/^[A-Z0-9_]+$/.test(name);
|
|
681
651
|
}
|
|
682
652
|
|
|
@@ -686,31 +656,23 @@ function isDefaultExportStatement(statement: ts.Statement) {
|
|
|
686
656
|
return !!ts.getModifiers(statement)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword);
|
|
687
657
|
}
|
|
688
658
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
* bang marker in browser-reachable code ships verbatim to every visitor. `no-bang-comment-in-client.grit`
|
|
692
|
-
* is the build gate; this exists because that rule cannot see the whole file.
|
|
693
|
-
*
|
|
694
|
-
* A comment is trivia, which Biome's GritQL exposes to no node pattern, so the rule matches through
|
|
695
|
-
* `file($name, $body)` — and `$body` is the module's *token* span. A marker above the first statement or
|
|
696
|
-
* below the last one sits in leading or trailing trivia and is unreachable from GritQL at all. Reading the
|
|
697
|
-
* raw text here covers both, and re-reporting the markers grit already catches costs nothing: the two run
|
|
698
|
-
* from different commands, and a file with one is failing lint either way.
|
|
699
|
-
*/
|
|
659
|
+
// Bun keeps `//!` and `/*!` through minification. GritQL's `file($name, $body)` spans tokens only, so the grit rule
|
|
660
|
+
// misses a marker in the file's leading or trailing trivia; the raw text is read here.
|
|
700
661
|
function getBangCommentWarnings(sourceFile: SourceFileInfo): QualityWarning[] {
|
|
701
662
|
if (!isClientReachableFile(sourceFile.file)) return [];
|
|
702
663
|
// Anchored to line start or to whitespace after code, so a literal like `"https://host//!path"` is not a hit.
|
|
703
|
-
return findPatternLines(sourceFile.content, /(?:^|[^\s/][ \t]+)\/[*/]!/).map((line) =>
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
664
|
+
return findPatternLines(sourceFile.content, /(?:^|[^\s/][ \t]+)\/[*/]!/).map((line) =>
|
|
665
|
+
fileWarning(
|
|
666
|
+
"akan.file.bang-comment-in-client",
|
|
667
|
+
"file",
|
|
668
|
+
sourceFile.file,
|
|
669
|
+
"A `//!` or `/*!` marker survives minification and ships to the browser.",
|
|
670
|
+
line,
|
|
671
|
+
),
|
|
672
|
+
);
|
|
711
673
|
}
|
|
712
674
|
|
|
713
|
-
|
|
675
|
+
// Mirrors the path scope of `no-bang-comment-in-client.grit` in `biome.base.json`.
|
|
714
676
|
function isClientReachableFile(file: string) {
|
|
715
677
|
if (/\.(test|spec)\.tsx?$/.test(file)) return false;
|
|
716
678
|
if (/\.(constant|store)\.ts$/.test(file)) return true;
|
|
@@ -725,67 +687,53 @@ function getPlaceholderExportWarnings(sourceFile: SourceFileInfo): QualityWarnin
|
|
|
725
687
|
if (!sourceFile.file.endsWith("/index.ts") && !sourceFile.file.endsWith("/index.tsx")) return [];
|
|
726
688
|
return getTopLevelDeclarations(sourceFile)
|
|
727
689
|
.filter((declaration) => declaration.exported && PLACEHOLDER_EXPORT_NAMES.has(declaration.name))
|
|
728
|
-
.map((declaration) =>
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
690
|
+
.map((declaration) =>
|
|
691
|
+
fileWarning(
|
|
692
|
+
"akan.file.placeholder-export",
|
|
693
|
+
"file",
|
|
694
|
+
sourceFile.file,
|
|
695
|
+
`Generated or barrel index file should not export placeholder "${declaration.name}".`,
|
|
696
|
+
declaration.line,
|
|
697
|
+
),
|
|
698
|
+
);
|
|
736
699
|
}
|
|
737
700
|
|
|
738
701
|
function getDictionaryTextWarnings(sourceFile: SourceFileInfo): QualityWarning[] {
|
|
739
702
|
if (!sourceFile.file.endsWith(".dictionary.ts")) return [];
|
|
740
703
|
const stalePatterns = [{ pattern: /\b[A-Z][A-Za-z0-9]* description\b/, label: "scaffold description text" }];
|
|
741
704
|
return stalePatterns.flatMap(({ pattern, label }) =>
|
|
742
|
-
findPatternLines(sourceFile.content, pattern).map((line) =>
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
705
|
+
findPatternLines(sourceFile.content, pattern).map((line) =>
|
|
706
|
+
fileWarning(
|
|
707
|
+
"akan.file.dictionary-stale-text",
|
|
708
|
+
"file",
|
|
709
|
+
sourceFile.file,
|
|
710
|
+
`Dictionary text appears to contain ${label}.`,
|
|
711
|
+
line,
|
|
712
|
+
),
|
|
713
|
+
),
|
|
750
714
|
);
|
|
751
715
|
}
|
|
752
716
|
|
|
753
717
|
function getGlobalMutationWarnings(sourceFile: SourceFileInfo): QualityWarning[] {
|
|
754
718
|
const warnings: QualityWarning[] = [];
|
|
755
719
|
for (const statement of sourceFile.sourceFile.statements) {
|
|
756
|
-
|
|
757
|
-
warnings.push(
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
766
|
-
if (ts.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
|
|
767
|
-
warnings.push({
|
|
768
|
-
rule: "akan.file.window-augmentation",
|
|
769
|
-
scope: "file",
|
|
770
|
-
severity: "warning",
|
|
771
|
-
file: sourceFile.file,
|
|
772
|
-
line: getLine(sourceFile.sourceFile, statement),
|
|
773
|
-
message: "Window augmentation should be isolated to approved browser integration files.",
|
|
774
|
-
});
|
|
775
|
-
}
|
|
720
|
+
const warn = (rule: string, message: string) =>
|
|
721
|
+
warnings.push(fileWarning(rule, "file", sourceFile.file, message, getLine(sourceFile.sourceFile, statement)));
|
|
722
|
+
if (ts.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global")
|
|
723
|
+
warn("akan.file.global-declaration", "Global declarations require an explicit low-level integration allowlist.");
|
|
724
|
+
if (ts.isInterfaceDeclaration(statement) && statement.name.text === "Window")
|
|
725
|
+
warn(
|
|
726
|
+
"akan.file.window-augmentation",
|
|
727
|
+
"Window augmentation should be isolated to approved browser integration files.",
|
|
728
|
+
);
|
|
776
729
|
if (
|
|
777
730
|
ts.isExpressionStatement(statement) &&
|
|
778
731
|
statement.expression.getText(sourceFile.sourceFile).includes(".prototype.")
|
|
779
|
-
)
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
file: sourceFile.file,
|
|
785
|
-
line: getLine(sourceFile.sourceFile, statement),
|
|
786
|
-
message: "Prototype mutation should be avoided or isolated to approved low-level integration files.",
|
|
787
|
-
});
|
|
788
|
-
}
|
|
732
|
+
)
|
|
733
|
+
warn(
|
|
734
|
+
"akan.file.prototype-mutation",
|
|
735
|
+
"Prototype mutation should be avoided or isolated to approved low-level integration files.",
|
|
736
|
+
);
|
|
789
737
|
}
|
|
790
738
|
return warnings;
|
|
791
739
|
}
|
|
@@ -798,35 +746,35 @@ function getTopLevelDeclarations(sourceFile: SourceFileInfo): TopLevelDeclaratio
|
|
|
798
746
|
|
|
799
747
|
function getTopLevelDeclaration(sourceFile: ts.SourceFile, statement: ts.Statement): TopLevelDeclaration[] {
|
|
800
748
|
const line = getLine(sourceFile, statement);
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
if (ts.
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
if (ts.
|
|
814
|
-
return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
|
|
815
|
-
}
|
|
816
|
-
if (ts.isVariableStatement(statement)) {
|
|
749
|
+
const declared = (name: string, kind: string, at = line) => ({
|
|
750
|
+
name,
|
|
751
|
+
kind,
|
|
752
|
+
line: at,
|
|
753
|
+
exported: isExported(statement),
|
|
754
|
+
node: statement,
|
|
755
|
+
});
|
|
756
|
+
if (ts.isClassDeclaration(statement) && statement.name) return [declared(statement.name.text, "class")];
|
|
757
|
+
if (ts.isFunctionDeclaration(statement) && statement.name) return [declared(statement.name.text, "function")];
|
|
758
|
+
if (ts.isInterfaceDeclaration(statement)) return [declared(statement.name.text, "interface")];
|
|
759
|
+
if (ts.isTypeAliasDeclaration(statement)) return [declared(statement.name.text, "type")];
|
|
760
|
+
if (ts.isEnumDeclaration(statement)) return [declared(statement.name.text, "enum")];
|
|
761
|
+
if (ts.isVariableStatement(statement))
|
|
817
762
|
return statement.declarationList.declarations
|
|
818
763
|
.filter((declaration) => ts.isIdentifier(declaration.name))
|
|
819
|
-
.map((declaration) =>
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
exported: isExported(statement),
|
|
824
|
-
node: statement,
|
|
825
|
-
}));
|
|
826
|
-
}
|
|
827
|
-
if (ts.isExportDeclaration(statement)) {
|
|
764
|
+
.map((declaration) =>
|
|
765
|
+
declared((declaration.name as ts.Identifier).text, "variable", getLine(sourceFile, declaration)),
|
|
766
|
+
);
|
|
767
|
+
if (ts.isExportDeclaration(statement))
|
|
828
768
|
return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
|
|
829
|
-
|
|
769
|
+
return [];
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function getConventionClassNames(suffix: (typeof CONVENTION_SUFFIXES)[number], model: string) {
|
|
773
|
+
if (suffix === ".constant.ts") return [`${model}Input`, `${model}Object`, model, `Light${model}`, `${model}Insight`];
|
|
774
|
+
if (suffix === ".document.ts") return [`${model}Filter`, model, `${model}Model`];
|
|
775
|
+
if (suffix === ".service.ts") return [`${model}Service`];
|
|
776
|
+
if (suffix === ".signal.ts") return [`${model}Internal`, `${model}Slice`, `${model}Endpoint`];
|
|
777
|
+
if (suffix === ".store.ts") return [`${model}Store`];
|
|
830
778
|
return [];
|
|
831
779
|
}
|
|
832
780
|
|
|
@@ -836,45 +784,18 @@ function isAllowedConventionDeclaration(
|
|
|
836
784
|
declaration: TopLevelDeclaration,
|
|
837
785
|
) {
|
|
838
786
|
if (suffix === ".dictionary.ts") return isExportedConst(declaration) && declaration.name === "dictionary";
|
|
839
|
-
if (suffix === ".constant.ts") return isAllowedConstantDeclaration(modelName, declaration);
|
|
840
|
-
if (suffix === ".document.ts")
|
|
841
|
-
return (
|
|
842
|
-
declaration.kind === "class" && [`${modelName}Filter`, modelName, `${modelName}Model`].includes(declaration.name)
|
|
843
|
-
);
|
|
844
|
-
if (suffix === ".service.ts") return declaration.kind === "class" && declaration.name === `${modelName}Service`;
|
|
845
|
-
if (suffix === ".signal.ts")
|
|
846
|
-
return (
|
|
847
|
-
declaration.kind === "class" &&
|
|
848
|
-
[`${modelName}Internal`, `${modelName}Slice`, `${modelName}Endpoint`].includes(declaration.name)
|
|
849
|
-
);
|
|
850
|
-
if (suffix === ".store.ts") return declaration.kind === "class" && declaration.name === `${modelName}Store`;
|
|
851
|
-
return false;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
function isAllowedConstantDeclaration(modelName: string, declaration: TopLevelDeclaration) {
|
|
855
787
|
if (declaration.kind !== "class") return false;
|
|
856
|
-
if (
|
|
857
|
-
|
|
858
|
-
declaration.name,
|
|
859
|
-
)
|
|
860
|
-
)
|
|
861
|
-
return true;
|
|
862
|
-
if (!ts.isClassDeclaration(declaration.node)) return false;
|
|
863
|
-
const heritageClause = declaration.node.heritageClauses?.find(
|
|
864
|
-
(clause) => clause.token === ts.SyntaxKind.ExtendsKeyword,
|
|
865
|
-
);
|
|
866
|
-
const expression = heritageClause?.types[0]?.expression;
|
|
867
|
-
return !!expression && expression.getText().startsWith("enumOf(");
|
|
788
|
+
if (getConventionClassNames(suffix, modelName).includes(declaration.name)) return true;
|
|
789
|
+
return suffix === ".constant.ts" && isEnumClassStatement(declaration.node.getSourceFile(), declaration.node);
|
|
868
790
|
}
|
|
869
791
|
|
|
870
792
|
function getConventionDescription(suffix: (typeof CONVENTION_SUFFIXES)[number], modelName: string) {
|
|
871
793
|
if (suffix === ".dictionary.ts") return "export const dictionary";
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
return `${modelName}Store`;
|
|
794
|
+
const names = [
|
|
795
|
+
...getConventionClassNames(suffix, modelName),
|
|
796
|
+
...(suffix === ".constant.ts" ? ["enumOf classes"] : []),
|
|
797
|
+
];
|
|
798
|
+
return names.length > 1 ? `${names.slice(0, -1).join(", ")}, or ${names.at(-1)}` : names[0];
|
|
878
799
|
}
|
|
879
800
|
|
|
880
801
|
const sysRootTypes = { apps: "app", libs: "lib" } as const;
|
|
@@ -910,13 +831,12 @@ function getModuleUiWarning(file: string): QualityWarning | null {
|
|
|
910
831
|
: ["Template", "Unit"];
|
|
911
832
|
const allowedFileNames = new Set(allowedSuffixes.map((suffix) => `${pascalName}.${suffix}.tsx`));
|
|
912
833
|
if (allowedFileNames.has(fileName) || fileName.endsWith(".test.tsx") || fileName.endsWith(".spec.tsx")) return null;
|
|
913
|
-
return
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
severity: "warning",
|
|
834
|
+
return fileWarning(
|
|
835
|
+
"akan.layout.module-ui-file",
|
|
836
|
+
"layout",
|
|
917
837
|
file,
|
|
918
|
-
|
|
919
|
-
|
|
838
|
+
`Unexpected ${kind} module UI filename "${fileName}". Expected one of: ${[...allowedFileNames].join(", ")}.`,
|
|
839
|
+
);
|
|
920
840
|
}
|
|
921
841
|
|
|
922
842
|
function getModuleInfo(file: string) {
|
|
@@ -930,10 +850,7 @@ function getModuleInfo(file: string) {
|
|
|
930
850
|
}
|
|
931
851
|
if (segments.length !== libIndex + 3) return null;
|
|
932
852
|
const fileName = segments[libIndex + 2];
|
|
933
|
-
if (moduleName.startsWith("__")) {
|
|
934
|
-
const scalarName = moduleName === "__scalar" ? segments[libIndex + 2] : moduleName;
|
|
935
|
-
return { moduleName: scalarName, fileName, kind: "scalar" as const };
|
|
936
|
-
}
|
|
853
|
+
if (moduleName.startsWith("__")) return { moduleName, fileName, kind: "scalar" as const };
|
|
937
854
|
if (moduleName.startsWith("_")) return { moduleName, fileName, kind: "service" as const };
|
|
938
855
|
return { moduleName, fileName, kind: "database" as const };
|
|
939
856
|
}
|
|
@@ -947,10 +864,7 @@ function isExportedConst(declaration: TopLevelDeclaration) {
|
|
|
947
864
|
}
|
|
948
865
|
|
|
949
866
|
function isExported(node: ts.Node) {
|
|
950
|
-
return (
|
|
951
|
-
!!ts.getCombinedModifierFlags(node as ts.Declaration) &&
|
|
952
|
-
!!(ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export)
|
|
953
|
-
);
|
|
867
|
+
return !!(ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export);
|
|
954
868
|
}
|
|
955
869
|
|
|
956
870
|
function isFunctionLikeInitializer(node: ts.Expression | undefined) {
|