@svelte-vitals/core 0.31.1 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +36 -3
- package/dist/index.js +252 -23
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -474,6 +474,11 @@ interface ComponentFacts {
|
|
|
474
474
|
}[];
|
|
475
475
|
/** Inline `svelte-vitals-disable-next-line` directives found in this file's source — component-rule escape hatch (issue #92). Optional: absent is equivalent to no directives, so existing external constructors of `ComponentFacts` are unaffected. */
|
|
476
476
|
suppressions?: SuppressionDirective[];
|
|
477
|
+
/** Markdown links `[label](url)` appearing inside a comment (architecture/doc-link-target). */
|
|
478
|
+
commentLinks: {
|
|
479
|
+
url: string;
|
|
480
|
+
line: number;
|
|
481
|
+
}[];
|
|
477
482
|
}
|
|
478
483
|
|
|
479
484
|
/** What the per-file parsers produce — `ComponentFacts` minus `file`, with `suppressions` always present. */
|
|
@@ -544,6 +549,20 @@ interface KitModuleFacts {
|
|
|
544
549
|
name: string;
|
|
545
550
|
line: number;
|
|
546
551
|
}[];
|
|
552
|
+
/**
|
|
553
|
+
* `.set()`/`.update()` in a handler on an import resolving under the `$lib` server root.
|
|
554
|
+
* The call shape alone cannot tell a persistence client (`db.set(…)`) from a hand-rolled
|
|
555
|
+
* in-memory store, so the decision needs the target module — which this pure parse cannot
|
|
556
|
+
* read. `collectKitModuleFacts` resolves each one and promotes the in-memory ones into
|
|
557
|
+
* `importedStateWrites`; a consumer that ignores this field sees the pre-arbitration
|
|
558
|
+
* behaviour, i.e. every one of these exempt.
|
|
559
|
+
*/
|
|
560
|
+
pendingServerStoreWrites: {
|
|
561
|
+
name: string;
|
|
562
|
+
imported: string;
|
|
563
|
+
resolved: string;
|
|
564
|
+
line: number;
|
|
565
|
+
}[];
|
|
547
566
|
/** Value imports whose specifier resolves to a repo-local `.svelte.ts`/`.svelte.js` runes module (security/shared-state-import). */
|
|
548
567
|
runesModuleImports: {
|
|
549
568
|
source: string;
|
|
@@ -1252,6 +1271,8 @@ declare const architectureReservedDirectoryNames: Rule;
|
|
|
1252
1271
|
|
|
1253
1272
|
declare const architectureRouteComponentImport: Rule;
|
|
1254
1273
|
|
|
1274
|
+
declare const architectureDocLinkTarget: Rule;
|
|
1275
|
+
|
|
1255
1276
|
declare const performanceHeavyImport: Rule;
|
|
1256
1277
|
|
|
1257
1278
|
declare const performanceNamespaceImport: Rule;
|
|
@@ -1444,6 +1465,8 @@ interface ScoreResult {
|
|
|
1444
1465
|
}
|
|
1445
1466
|
interface ScoreOptions {
|
|
1446
1467
|
applyCriticalCap?: boolean;
|
|
1468
|
+
/** The rules that ran. Defaults to the selected registry; supplied by tests and custom rule sets. */
|
|
1469
|
+
rules?: readonly Rule[];
|
|
1447
1470
|
}
|
|
1448
1471
|
/** Compute the headline score and its breakdown (design §12). */
|
|
1449
1472
|
declare function computeScore(results: Result[], config: Config, options?: ScoreOptions): ScoreResult;
|
|
@@ -1473,6 +1496,15 @@ declare function issueOf(result: Result): {
|
|
|
1473
1496
|
type JsonIssue = ReturnType<typeof issueOf> & {
|
|
1474
1497
|
severity: ReturnType<typeof effectiveSeverity>;
|
|
1475
1498
|
};
|
|
1499
|
+
/**
|
|
1500
|
+
* Per-rule counts. A rule present with `findings: 0` ran and reported nothing, and an absent rule was not
|
|
1501
|
+
* selected — but only when the caller supplied `ruleIds`. Without it the map is seeded from results alone,
|
|
1502
|
+
* so absence means "produced nothing" rather than "not selected".
|
|
1503
|
+
*/
|
|
1504
|
+
interface RuleEvidence {
|
|
1505
|
+
findings: number;
|
|
1506
|
+
passed: number;
|
|
1507
|
+
}
|
|
1476
1508
|
interface JsonReport {
|
|
1477
1509
|
version: string;
|
|
1478
1510
|
score: number;
|
|
@@ -1482,6 +1514,7 @@ interface JsonReport {
|
|
|
1482
1514
|
scoreModel: ScoreModel;
|
|
1483
1515
|
}>;
|
|
1484
1516
|
summary: Summary;
|
|
1517
|
+
rules: Record<string, RuleEvidence>;
|
|
1485
1518
|
routes: Array<{
|
|
1486
1519
|
route: string;
|
|
1487
1520
|
score: number;
|
|
@@ -1492,11 +1525,11 @@ interface JsonReport {
|
|
|
1492
1525
|
/** Build the structured JSON report object (design §7). The shape the `json` reporter emits (issue #24). */
|
|
1493
1526
|
declare function buildJsonReport(results: Result[], config: Config, meta: {
|
|
1494
1527
|
version: string;
|
|
1495
|
-
}): JsonReport;
|
|
1528
|
+
}, ruleIds?: readonly string[]): JsonReport;
|
|
1496
1529
|
/** Render results as the documented JSON report string (design §7). */
|
|
1497
1530
|
declare function formatJsonReport(results: Result[], config: Config, meta: {
|
|
1498
1531
|
version: string;
|
|
1499
|
-
}): string;
|
|
1532
|
+
}, ruleIds?: readonly string[]): string;
|
|
1500
1533
|
|
|
1501
1534
|
/** Render failing findings as an agent-actionable Markdown remediation document (issue #18). */
|
|
1502
1535
|
declare function formatAgentReport(results: Result[], config: Config): string;
|
|
@@ -1588,4 +1621,4 @@ declare function escapeHtml(s: string): string;
|
|
|
1588
1621
|
*/
|
|
1589
1622
|
declare function safeHref(url: string): string | null;
|
|
1590
1623
|
|
|
1591
|
-
export { APP_SCRIPT, APP_STYLE, type AppSnapshot, BAND_COLOR, CATEGORIES, CHILD_NODE_KEYS, type Category, type Classification, type CompiledOverride, type ComponentFacts, type Config, type ConsoleReportOptions, type Detection, type EachBlockFact, type EffectFact, type Fix, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, type ImageInfo, type JsonReport, type KitAlias, type KitModuleFacts, type OrphanEffectFact, type Palette, type Presence, type Project, ROBOTS_SOURCE_PATHS, type RawKitAliases, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type Result, type RouteBadge, type Rule, type RuleContext, type RuleInfo, type RuleOptionInfo, type RuleOptionSpec, type RuleOptions, type RuleOptionsSpec, type RuleOverride, type RuleSetting, type RuleSettingObject, type Runtime, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type SourceSpan, type Summary, type SuppressionDirective, type TreatDynamicAs, VITE_CONFIG_FILES, type Value, type ViteKitConfigResult, allRules, applyOverrides, applyRuleSeverities, architectureComponentSize, architectureDirectoryNaming, architecturePrivateScopeImport, architecturePropCount, architectureReservedDirectoryNames, architectureRouteComponentImport, architectureUnitEntryFile, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, compileOverrides, computeHealth, computeScore, correctnessBasePathNavigation, correctnessCheckableBindValue, correctnessEachIndexKey, correctnessEachKey, correctnessEffectAsDerived, correctnessEffectAsOnMount, correctnessInstanceBrowserGlobal, correctnessNonreactiveBuiltinState, correctnessOrphanEffect, correctnessOrphanLifecycle, correctnessPropMutation, correctnessServerBrowserGlobal, correctnessStalePropDerivation, correctnessUnmutatedState, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, emptyComponentFacts, emptyKitModuleFacts, escapeHtml, explainRule, findAttr, findKitAliasesInSvelteConfig, findKitPathsBaseInSvelteConfig, findKitPathsBaseInViteConfig, findMinifyDisabled, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, intOption, isMentionedAnywhere, isPenalized, lineOf, linkRule, listOption, mapOption, noColorPalette, overrideMatches, parseComponentFacts, parseKitModuleFacts, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRuleOptions, resolveRunesModuleSpecifier, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, securityHandlerStateWrite, securityJavascriptUrl, securityRawHtml, securityServerModuleState, securitySharedStateImport, selectRules, seoCanonicalUrl, seoCharset, seoDescriptionLength, seoDescriptionPresence, seoDuplicateDescription, seoDuplicateTitle, seoHeadingLevelSkip, seoHreflang, seoHtmlLang, seoImageAlt, seoIndexability, seoJsonLd, seoJsonLdDateFormat, seoJsonLdDeprecatedType, seoJsonLdPlaceholder, seoJsonLdRelativeUrl, seoJsonLdRequiredProps, seoJsonLdValidity, seoOgDescription, seoOgImage, seoOgTitle, seoOgUrl, seoRobotsTxt, seoSingleH1, seoSitemapInRobots, seoSitemapXml, seoSsrDisabled, seoTitleLength, seoTitlePresence, seoTwitterCard, seoViewport, settingOptions, settingSeverity, shouldSkipRangeCheck, summarize, textFromNodes, validateRuleOptions, validateRuleSetting, valueFromNodes };
|
|
1624
|
+
export { APP_SCRIPT, APP_STYLE, type AppSnapshot, BAND_COLOR, CATEGORIES, CHILD_NODE_KEYS, type Category, type Classification, type CompiledOverride, type ComponentFacts, type Config, type ConsoleReportOptions, type Detection, type EachBlockFact, type EffectFact, type Fix, type HeadProvider, type HeadTag, type HeadingInfo, type HealthResult, type ImageInfo, type JsonReport, type KitAlias, type KitModuleFacts, type OrphanEffectFact, type Palette, type Presence, type Project, ROBOTS_SOURCE_PATHS, type RawKitAliases, type ResolvedHead, type ResolvedHeadings, type ResolvedImages, type Result, type RouteBadge, type Rule, type RuleContext, type RuleEvidence, type RuleInfo, type RuleOptionInfo, type RuleOptionSpec, type RuleOptions, type RuleOptionsSpec, type RuleOverride, type RuleSetting, type RuleSettingObject, type Runtime, SITEMAP_SOURCE_PATHS, SVELTE_CONFIG_FILES, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type SourceSpan, type Summary, type SuppressionDirective, type TreatDynamicAs, VITE_CONFIG_FILES, type Value, type ViteKitConfigResult, allRules, applyOverrides, applyRuleSeverities, architectureComponentSize, architectureDirectoryNaming, architectureDocLinkTarget, architecturePrivateScopeImport, architecturePropCount, architectureReservedDirectoryNames, architectureRouteComponentImport, architectureUnitEntryFile, attrText, attrTextOf, attrValue, attrValueOf, buildHtmlDocument, buildJsonReport, classify, collectComponentFacts, collectKitModuleFacts, collectSourceFiles, compileOverrides, computeHealth, computeScore, correctnessBasePathNavigation, correctnessCheckableBindValue, correctnessEachIndexKey, correctnessEachKey, correctnessEffectAsDerived, correctnessEffectAsOnMount, correctnessInstanceBrowserGlobal, correctnessNonreactiveBuiltinState, correctnessOrphanEffect, correctnessOrphanLifecycle, correctnessPropMutation, correctnessServerBrowserGlobal, correctnessStalePropDerivation, correctnessUnmutatedState, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, emptyComponentFacts, emptyKitModuleFacts, escapeHtml, explainRule, findAttr, findKitAliasesInSvelteConfig, findKitPathsBaseInSvelteConfig, findKitPathsBaseInViteConfig, findMinifyDisabled, formatAgentReport, formatConsoleReport, formatGithubReport, formatHtmlReport, formatJsonReport, formatMarkdownReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, intOption, isMentionedAnywhere, isPenalized, lineOf, linkRule, listOption, mapOption, noColorPalette, overrideMatches, parseComponentFacts, parseKitModuleFacts, performanceFontPreloadCrossorigin, performanceHeavyImport, performanceImageDimensions, performanceImageLoadingHint, performanceLcpImage, performanceLoadWaterfall, performanceMinifyDisabled, performanceNamespaceImport, performancePreconnect, performancePreloadMissingAs, performanceRenderBlockingScript, performanceResponsiveImage, performanceSequentialAwaits, performanceStateRaw, renderAppShell, resolveKitAliases, resolveKitPathsBase, resolveRuleOptions, resolveRunesModuleSpecifier, runRules, safeHref, scoreBand, scoreColor, scoresByCategory, securityHandlerStateWrite, securityJavascriptUrl, securityRawHtml, securityServerModuleState, securitySharedStateImport, selectRules, seoCanonicalUrl, seoCharset, seoDescriptionLength, seoDescriptionPresence, seoDuplicateDescription, seoDuplicateTitle, seoHeadingLevelSkip, seoHreflang, seoHtmlLang, seoImageAlt, seoIndexability, seoJsonLd, seoJsonLdDateFormat, seoJsonLdDeprecatedType, seoJsonLdPlaceholder, seoJsonLdRelativeUrl, seoJsonLdRequiredProps, seoJsonLdValidity, seoOgDescription, seoOgImage, seoOgTitle, seoOgUrl, seoRobotsTxt, seoSingleH1, seoSitemapInRobots, seoSitemapXml, seoSsrDisabled, seoTitleLength, seoTitlePresence, seoTwitterCard, seoViewport, settingOptions, settingSeverity, shouldSkipRangeCheck, summarize, textFromNodes, validateRuleOptions, validateRuleSetting, valueFromNodes };
|
package/dist/index.js
CHANGED
|
@@ -904,6 +904,53 @@ function collectSuppressions(source) {
|
|
|
904
904
|
});
|
|
905
905
|
return out;
|
|
906
906
|
}
|
|
907
|
+
var MD_LINK = /\[[^\]]*\]\(([^)\s]+)\)/g;
|
|
908
|
+
var SCRIPT_OPEN = /<script(?:\s[^>]*)?>/;
|
|
909
|
+
var SCRIPT_CLOSE = /<\/script\s*>/;
|
|
910
|
+
var STYLE_OPEN = /<style(?:\s[^>]*)?>/;
|
|
911
|
+
var STYLE_CLOSE = /<\/style\s*>/;
|
|
912
|
+
function collectCommentLinks(source, { wholeFileIsScript = false } = {}) {
|
|
913
|
+
const out = [];
|
|
914
|
+
let htmlOpen = false;
|
|
915
|
+
let block = wholeFileIsScript ? "script" : void 0;
|
|
916
|
+
source.split("\n").forEach((line, i) => {
|
|
917
|
+
let text = "";
|
|
918
|
+
if (block !== void 0) {
|
|
919
|
+
if (block === "script" && /^\s*\/\//.test(line)) text = line.replace(/^\s*\/\//, "");
|
|
920
|
+
if (!wholeFileIsScript && (block === "script" ? SCRIPT_CLOSE : STYLE_CLOSE).test(line)) block = void 0;
|
|
921
|
+
} else {
|
|
922
|
+
let plain = "";
|
|
923
|
+
let rest = line;
|
|
924
|
+
while (rest.length > 0) {
|
|
925
|
+
if (htmlOpen) {
|
|
926
|
+
const end = rest.indexOf("-->");
|
|
927
|
+
if (end === -1) {
|
|
928
|
+
text += rest;
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
text += rest.slice(0, end);
|
|
932
|
+
htmlOpen = false;
|
|
933
|
+
rest = rest.slice(end + 3);
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
const start = rest.indexOf("<!--");
|
|
937
|
+
if (start === -1) {
|
|
938
|
+
plain += rest;
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
plain += rest.slice(0, start);
|
|
942
|
+
htmlOpen = true;
|
|
943
|
+
rest = rest.slice(start + 4);
|
|
944
|
+
}
|
|
945
|
+
const opened = SCRIPT_OPEN.test(plain) ? "script" : STYLE_OPEN.test(plain) ? "style" : void 0;
|
|
946
|
+
if (opened !== void 0 && !(opened === "script" ? SCRIPT_CLOSE : STYLE_CLOSE).test(plain)) block = opened;
|
|
947
|
+
}
|
|
948
|
+
for (const m of text.matchAll(MD_LINK)) {
|
|
949
|
+
if (m[1] !== void 0) out.push({ url: m[1], line: i + 1 });
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
return out;
|
|
953
|
+
}
|
|
907
954
|
var EVAL_SCOPE_BOUNDARIES = /* @__PURE__ */ new Set([
|
|
908
955
|
"FunctionDeclaration",
|
|
909
956
|
"FunctionExpression",
|
|
@@ -1274,6 +1321,7 @@ function parseModuleFacts(source, filename) {
|
|
|
1274
1321
|
checkableBindValues: [],
|
|
1275
1322
|
basePathLinks,
|
|
1276
1323
|
suppressions: collectSuppressions(source),
|
|
1324
|
+
commentLinks: collectCommentLinks(source, { wholeFileIsScript: true }),
|
|
1277
1325
|
orphanEffects,
|
|
1278
1326
|
orphanLifecycleCalls,
|
|
1279
1327
|
browserGlobalRefs,
|
|
@@ -1480,7 +1528,8 @@ function parseComponentFacts(source, filename) {
|
|
|
1480
1528
|
orphanLifecycleCalls,
|
|
1481
1529
|
browserGlobalRefs,
|
|
1482
1530
|
moduleStateDecls: [],
|
|
1483
|
-
suppressions
|
|
1531
|
+
suppressions,
|
|
1532
|
+
commentLinks: collectCommentLinks(source)
|
|
1484
1533
|
};
|
|
1485
1534
|
}
|
|
1486
1535
|
|
|
@@ -1508,7 +1557,8 @@ function emptyComponentFacts(file) {
|
|
|
1508
1557
|
orphanLifecycleCalls: [],
|
|
1509
1558
|
browserGlobalRefs: [],
|
|
1510
1559
|
moduleStateDecls: [],
|
|
1511
|
-
suppressions: []
|
|
1560
|
+
suppressions: [],
|
|
1561
|
+
commentLinks: []
|
|
1512
1562
|
};
|
|
1513
1563
|
}
|
|
1514
1564
|
async function collectComponentFacts(rt, cwd) {
|
|
@@ -1842,6 +1892,13 @@ function libServerRoot(aliases) {
|
|
|
1842
1892
|
if (lib && lib.replacement === null) return void 0;
|
|
1843
1893
|
return `${lib?.replacement ?? "src/lib"}/server`;
|
|
1844
1894
|
}
|
|
1895
|
+
function serverRootRelativePath(spec, importerFile, aliases) {
|
|
1896
|
+
const serverRoot = libServerRoot(aliases);
|
|
1897
|
+
if (serverRoot === void 0) return void 0;
|
|
1898
|
+
const path = resolveRepoLocalPath(spec, importerFile, aliases);
|
|
1899
|
+
if (path === void 0) return void 0;
|
|
1900
|
+
return path === serverRoot || path.startsWith(`${serverRoot}/`) ? path : void 0;
|
|
1901
|
+
}
|
|
1845
1902
|
function isLocalStateSpecifier(spec, importerFile, aliases) {
|
|
1846
1903
|
const serverRoot = libServerRoot(aliases);
|
|
1847
1904
|
if (serverRoot === void 0) return false;
|
|
@@ -1849,12 +1906,32 @@ function isLocalStateSpecifier(spec, importerFile, aliases) {
|
|
|
1849
1906
|
if (path === void 0) return false;
|
|
1850
1907
|
return path !== serverRoot && !path.startsWith(`${serverRoot}/`);
|
|
1851
1908
|
}
|
|
1909
|
+
var IN_MEMORY_CTORS = /* @__PURE__ */ new Set(["Map", "Set", "WeakMap", "WeakSet"]);
|
|
1910
|
+
function isInMemoryInit(init) {
|
|
1911
|
+
if (!init) return false;
|
|
1912
|
+
if (init.type === "ObjectExpression" || init.type === "ArrayExpression") return true;
|
|
1913
|
+
return init.type === "NewExpression" && init.callee?.type === "Identifier" && IN_MEMORY_CTORS.has(init.callee.name);
|
|
1914
|
+
}
|
|
1915
|
+
function parseInMemoryExports(source, filename) {
|
|
1916
|
+
const names = /* @__PURE__ */ new Set();
|
|
1917
|
+
const { program } = parseModuleProgram(source, filename);
|
|
1918
|
+
for (const stmt of program?.body ?? []) {
|
|
1919
|
+
if (stmt?.type !== "ExportNamedDeclaration" || !stmt.declaration) continue;
|
|
1920
|
+
const decl = stmt.declaration;
|
|
1921
|
+
if (decl.type !== "VariableDeclaration") continue;
|
|
1922
|
+
for (const d of decl.declarations ?? []) {
|
|
1923
|
+
if (d?.id?.type === "Identifier" && isInMemoryInit(d.init)) names.add(d.id.name);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
return names;
|
|
1927
|
+
}
|
|
1852
1928
|
function parseKitModuleFacts(source, filename, aliases) {
|
|
1853
1929
|
const suppressions = collectSuppressions(source);
|
|
1854
1930
|
const { program, wrapped } = parseModuleProgram(source, filename);
|
|
1855
1931
|
const moduleStateReassignments = [];
|
|
1856
1932
|
const importedStateWrites = [];
|
|
1857
1933
|
const importedStateWritesOutsideHandlers = [];
|
|
1934
|
+
const pendingServerStoreWrites = [];
|
|
1858
1935
|
const runesModuleImports = [];
|
|
1859
1936
|
const lifecycleCalls = [];
|
|
1860
1937
|
const browserGlobalRefs = [];
|
|
@@ -1863,6 +1940,7 @@ function parseKitModuleFacts(source, filename, aliases) {
|
|
|
1863
1940
|
moduleStateReassignments,
|
|
1864
1941
|
importedStateWrites,
|
|
1865
1942
|
importedStateWritesOutsideHandlers,
|
|
1943
|
+
pendingServerStoreWrites,
|
|
1866
1944
|
runesModuleImports,
|
|
1867
1945
|
lifecycleCalls,
|
|
1868
1946
|
browserGlobalRefs,
|
|
@@ -1872,6 +1950,7 @@ function parseKitModuleFacts(source, filename, aliases) {
|
|
|
1872
1950
|
}
|
|
1873
1951
|
const line = (start) => Math.max(0, lineOf(wrapped, start) - 1);
|
|
1874
1952
|
const importedSpecifiers = /* @__PURE__ */ new Map();
|
|
1953
|
+
const importedNames = /* @__PURE__ */ new Map();
|
|
1875
1954
|
for (const stmt of program.body ?? []) {
|
|
1876
1955
|
if (stmt?.type !== "ImportDeclaration" || stmt.importKind === "type") continue;
|
|
1877
1956
|
const spec = typeof stmt.source?.value === "string" ? stmt.source.value : "";
|
|
@@ -1880,6 +1959,10 @@ function parseKitModuleFacts(source, filename, aliases) {
|
|
|
1880
1959
|
if (s?.importKind === "type" || s?.local?.type !== "Identifier") continue;
|
|
1881
1960
|
names.push(s.local.name);
|
|
1882
1961
|
importedSpecifiers.set(s.local.name, spec);
|
|
1962
|
+
importedNames.set(
|
|
1963
|
+
s.local.name,
|
|
1964
|
+
s.type === "ImportSpecifier" && s.imported?.type === "Identifier" ? s.imported.name : s.local.name
|
|
1965
|
+
);
|
|
1883
1966
|
}
|
|
1884
1967
|
if (names.length === 0) continue;
|
|
1885
1968
|
const resolved = resolveRunesModuleSpecifier(spec, filename, aliases);
|
|
@@ -1956,8 +2039,21 @@ function parseKitModuleFacts(source, filename, aliases) {
|
|
|
1956
2039
|
const method = n.callee.property?.type === "Identifier" ? n.callee.property.name : void 0;
|
|
1957
2040
|
if (method === "set" || method === "update") {
|
|
1958
2041
|
const r = importedRoot(n.callee.object);
|
|
1959
|
-
|
|
1960
|
-
|
|
2042
|
+
const spec = r ? importedSpecifiers.get(r) : void 0;
|
|
2043
|
+
if (r && spec !== void 0) {
|
|
2044
|
+
if (isLocalStateSpecifier(spec, filename, aliases)) write = { name: r, via: "set-call" };
|
|
2045
|
+
else {
|
|
2046
|
+
const resolved = serverRootRelativePath(spec, filename, aliases);
|
|
2047
|
+
if (resolved !== void 0 && inHandler) {
|
|
2048
|
+
pendingServerStoreWrites.push({
|
|
2049
|
+
name: r,
|
|
2050
|
+
imported: importedNames.get(r) ?? r,
|
|
2051
|
+
resolved,
|
|
2052
|
+
line: line(n.start)
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
1961
2057
|
}
|
|
1962
2058
|
} else if (n.type === "AssignmentExpression" && (n.left?.type === "ObjectPattern" || n.left?.type === "ArrayPattern")) {
|
|
1963
2059
|
const scanPatternTargets = (pat) => {
|
|
@@ -2005,6 +2101,7 @@ function parseKitModuleFacts(source, filename, aliases) {
|
|
|
2005
2101
|
moduleStateReassignments: byLine(moduleStateReassignments),
|
|
2006
2102
|
importedStateWrites: byLine(importedStateWrites),
|
|
2007
2103
|
importedStateWritesOutsideHandlers: byLine(importedStateWritesOutsideHandlers),
|
|
2104
|
+
pendingServerStoreWrites: byLine(pendingServerStoreWrites),
|
|
2008
2105
|
runesModuleImports: byLine(runesModuleImports),
|
|
2009
2106
|
lifecycleCalls: byLine(lifecycleCalls),
|
|
2010
2107
|
browserGlobalRefs: byLine(browserGlobalRefs),
|
|
@@ -2024,6 +2121,7 @@ function emptyKitModuleFacts(file, kind) {
|
|
|
2024
2121
|
moduleStateReassignments: [],
|
|
2025
2122
|
importedStateWrites: [],
|
|
2026
2123
|
importedStateWritesOutsideHandlers: [],
|
|
2124
|
+
pendingServerStoreWrites: [],
|
|
2027
2125
|
runesModuleImports: [],
|
|
2028
2126
|
lifecycleCalls: [],
|
|
2029
2127
|
browserGlobalRefs: [],
|
|
@@ -2044,7 +2142,7 @@ async function collectKitModuleFacts(rt, cwd, aliases) {
|
|
|
2044
2142
|
];
|
|
2045
2143
|
const lists = await Promise.all(patterns.map((p) => rt.glob(p, cwd)));
|
|
2046
2144
|
const files = [...new Set(lists.flat())];
|
|
2047
|
-
|
|
2145
|
+
const facts = await Promise.all(
|
|
2048
2146
|
files.sort().map(async (rel) => {
|
|
2049
2147
|
const kind = kindOf(rel);
|
|
2050
2148
|
try {
|
|
@@ -2055,6 +2153,38 @@ async function collectKitModuleFacts(rt, cwd, aliases) {
|
|
|
2055
2153
|
}
|
|
2056
2154
|
})
|
|
2057
2155
|
);
|
|
2156
|
+
return arbitrateServerStoreWrites(rt, cwd, facts);
|
|
2157
|
+
}
|
|
2158
|
+
function moduleCandidates(repoPath) {
|
|
2159
|
+
if (repoPath.endsWith(".js")) return [repoPath, `${repoPath.slice(0, -3)}.ts`];
|
|
2160
|
+
if (repoPath.endsWith(".ts")) return [repoPath];
|
|
2161
|
+
return [`${repoPath}.ts`, `${repoPath}.js`, `${repoPath}/index.ts`, `${repoPath}/index.js`];
|
|
2162
|
+
}
|
|
2163
|
+
async function inMemoryExportsOf(rt, cwd, repoPath) {
|
|
2164
|
+
for (const rel of moduleCandidates(repoPath)) {
|
|
2165
|
+
try {
|
|
2166
|
+
if (!await rt.exists(rt.join(cwd, rel))) continue;
|
|
2167
|
+
return parseInMemoryExports(await rt.readFile(rt.join(cwd, rel)), rel);
|
|
2168
|
+
} catch {
|
|
2169
|
+
return /* @__PURE__ */ new Set();
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
return /* @__PURE__ */ new Set();
|
|
2173
|
+
}
|
|
2174
|
+
async function arbitrateServerStoreWrites(rt, cwd, facts) {
|
|
2175
|
+
const targets = [...new Set(facts.flatMap((f) => f.pendingServerStoreWrites.map((w) => w.resolved)))];
|
|
2176
|
+
if (targets.length === 0) return facts;
|
|
2177
|
+
const byPath = new Map(
|
|
2178
|
+
await Promise.all(targets.map(async (t) => [t, await inMemoryExportsOf(rt, cwd, t)]))
|
|
2179
|
+
);
|
|
2180
|
+
return facts.map((f) => {
|
|
2181
|
+
const promoted = f.pendingServerStoreWrites.filter((w) => byPath.get(w.resolved)?.has(w.imported)).map((w) => ({ name: w.name, line: w.line, via: "set-call" }));
|
|
2182
|
+
if (promoted.length === 0) return f;
|
|
2183
|
+
return {
|
|
2184
|
+
...f,
|
|
2185
|
+
importedStateWrites: [...f.importedStateWrites, ...promoted].sort((a, b) => a.line - b.line)
|
|
2186
|
+
};
|
|
2187
|
+
});
|
|
2058
2188
|
}
|
|
2059
2189
|
|
|
2060
2190
|
// src/config-object.ts
|
|
@@ -5336,6 +5466,60 @@ var architectureRouteComponentImport = componentRule({
|
|
|
5336
5466
|
}
|
|
5337
5467
|
});
|
|
5338
5468
|
|
|
5469
|
+
// src/rules/architecture/doc-link-target.ts
|
|
5470
|
+
var ID8 = "architecture/doc-link-target";
|
|
5471
|
+
function stripFragment(url) {
|
|
5472
|
+
const i = url.search(/[#?]/);
|
|
5473
|
+
return i === -1 ? url : url.slice(0, i);
|
|
5474
|
+
}
|
|
5475
|
+
function baseOf(root) {
|
|
5476
|
+
return root.replace(/\/+$/, "");
|
|
5477
|
+
}
|
|
5478
|
+
function remainderUnder(url, root) {
|
|
5479
|
+
const base = baseOf(root);
|
|
5480
|
+
if (url === base) return "";
|
|
5481
|
+
return url.startsWith(`${base}/`) ? baseOf(url.slice(base.length + 1)) : void 0;
|
|
5482
|
+
}
|
|
5483
|
+
function isUnderSrc(target) {
|
|
5484
|
+
return target.startsWith("src/");
|
|
5485
|
+
}
|
|
5486
|
+
function rootFor(url, roots) {
|
|
5487
|
+
let best;
|
|
5488
|
+
for (const r of roots) {
|
|
5489
|
+
const remainder = remainderUnder(url, r);
|
|
5490
|
+
if (remainder === void 0) continue;
|
|
5491
|
+
const base = baseOf(r);
|
|
5492
|
+
if (best === void 0 || base.length > best.base.length) best = { base, remainder };
|
|
5493
|
+
}
|
|
5494
|
+
return best;
|
|
5495
|
+
}
|
|
5496
|
+
function targetExists(path, sourceFiles) {
|
|
5497
|
+
const prefix = `${path}/`;
|
|
5498
|
+
return sourceFiles.some((f) => f === path || f.startsWith(prefix));
|
|
5499
|
+
}
|
|
5500
|
+
function references(links, roots) {
|
|
5501
|
+
const out = [];
|
|
5502
|
+
for (const { url, line } of links) {
|
|
5503
|
+
const match = rootFor(stripFragment(url), roots);
|
|
5504
|
+
if (match === void 0) continue;
|
|
5505
|
+
if (!isUnderSrc(match.remainder)) continue;
|
|
5506
|
+
out.push({ line, target: match.remainder });
|
|
5507
|
+
}
|
|
5508
|
+
return out;
|
|
5509
|
+
}
|
|
5510
|
+
var architectureDocLinkTarget = componentRule({
|
|
5511
|
+
id: ID8,
|
|
5512
|
+
title: "Documentation link target",
|
|
5513
|
+
category: "architecture",
|
|
5514
|
+
severity: "info",
|
|
5515
|
+
label: "Documentation link targets",
|
|
5516
|
+
options: { urlRoots: { kind: "string-list", default: [] } },
|
|
5517
|
+
recommendation: "Point the link at the unit that exists now, or remove it. A link inside a comment has nothing to resolve it, so a rename leaves it silently broken.",
|
|
5518
|
+
rationale: "A documentation link written in a comment is invisible to type checking, module resolution and the test runner, so a convention-driven rename leaves it pointing at nothing and only human review notices.",
|
|
5519
|
+
applies: (c, o, ctx) => ctx.sourceFiles !== void 0 && references(c.commentLinks, listOption(o, "urlRoots")).length > 0,
|
|
5520
|
+
bad: (c, o, ctx) => references(c.commentLinks, listOption(o, "urlRoots")).filter(({ target }) => !targetExists(target, ctx.sourceFiles ?? [])).map(({ line, target }) => ({ line, message: `${target} does not exist` }))
|
|
5521
|
+
});
|
|
5522
|
+
|
|
5339
5523
|
// src/rules/perf/heavy-import.ts
|
|
5340
5524
|
var HEAVY_PACKAGES = {
|
|
5341
5525
|
lodash: "import a submodule (lodash/debounce) or use lodash-es for tree-shaking",
|
|
@@ -5552,6 +5736,7 @@ var allRules = [
|
|
|
5552
5736
|
architectureDirectoryNaming,
|
|
5553
5737
|
architectureReservedDirectoryNames,
|
|
5554
5738
|
architectureRouteComponentImport,
|
|
5739
|
+
architectureDocLinkTarget,
|
|
5555
5740
|
performanceHeavyImport,
|
|
5556
5741
|
performanceNamespaceImport,
|
|
5557
5742
|
performanceMinifyDisabled,
|
|
@@ -5612,8 +5797,31 @@ function hasFailureAtOrAbove(summary, min) {
|
|
|
5612
5797
|
return order.some((sev, idx) => idx >= threshold && summary[sev] > 0);
|
|
5613
5798
|
}
|
|
5614
5799
|
|
|
5615
|
-
// src/scoring/
|
|
5800
|
+
// src/scoring/inventory.ts
|
|
5616
5801
|
var DEDUCTION = { critical: 15, warning: 5, info: 1 };
|
|
5802
|
+
function pairKey(category, scope) {
|
|
5803
|
+
return `${category}::${scope}`;
|
|
5804
|
+
}
|
|
5805
|
+
function severityOf(rule, config) {
|
|
5806
|
+
const setting = settingSeverity(config.rules[rule.id]);
|
|
5807
|
+
if (setting === "off") return void 0;
|
|
5808
|
+
return setting ?? rule.severity;
|
|
5809
|
+
}
|
|
5810
|
+
function buildInventory(config, rules = selectRules(allRules, config)) {
|
|
5811
|
+
const out = /* @__PURE__ */ new Map();
|
|
5812
|
+
for (const rule of rules) {
|
|
5813
|
+
const severity = severityOf(rule, config);
|
|
5814
|
+
if (severity === void 0) continue;
|
|
5815
|
+
const key = pairKey(rule.category, rule.scope);
|
|
5816
|
+
out.set(key, (out.get(key) ?? 0) + DEDUCTION[severity]);
|
|
5817
|
+
}
|
|
5818
|
+
return out;
|
|
5819
|
+
}
|
|
5820
|
+
function ruleScopes(rules) {
|
|
5821
|
+
return new Map(rules.map((r) => [r.id, pairKey(r.category, r.scope)]));
|
|
5822
|
+
}
|
|
5823
|
+
|
|
5824
|
+
// src/scoring/score.ts
|
|
5617
5825
|
var CRITICAL_CAP = 79;
|
|
5618
5826
|
function clamp(n) {
|
|
5619
5827
|
return Math.max(0, Math.min(100, n));
|
|
@@ -5621,27 +5829,36 @@ function clamp(n) {
|
|
|
5621
5829
|
function computeScore(results, config, options = {}) {
|
|
5622
5830
|
const routeResults = results.filter((r) => r.route !== void 0);
|
|
5623
5831
|
const projectResults = results.filter((r) => r.route === void 0);
|
|
5624
|
-
const
|
|
5625
|
-
|
|
5832
|
+
const rules = selectRules([...options.rules ?? allRules], config);
|
|
5833
|
+
const inventory = buildInventory(config, rules);
|
|
5834
|
+
const pairOf = ruleScopes(rules);
|
|
5626
5835
|
let anyCritical = false;
|
|
5627
|
-
const
|
|
5836
|
+
const observed = /* @__PURE__ */ new Map();
|
|
5837
|
+
const ruleMax = /* @__PURE__ */ new Map();
|
|
5628
5838
|
for (const r of routeResults) {
|
|
5839
|
+
const key = r.route;
|
|
5840
|
+
if (!observed.has(key)) observed.set(key, /* @__PURE__ */ new Set());
|
|
5841
|
+
const pair = pairOf.get(r.id);
|
|
5842
|
+
if (pair !== void 0) observed.get(key).add(pair);
|
|
5629
5843
|
if (!isPenalized(r.detection, config.treatDynamicAs)) continue;
|
|
5630
5844
|
const sev = effectiveSeverity(r, config);
|
|
5631
5845
|
if (sev === "critical") anyCritical = true;
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
if (!perRule) routeRuleMax.set(route, perRule = /* @__PURE__ */ new Map());
|
|
5846
|
+
let perRule = ruleMax.get(key);
|
|
5847
|
+
if (!perRule) ruleMax.set(key, perRule = /* @__PURE__ */ new Map());
|
|
5635
5848
|
const prev = perRule.get(r.id) ?? 0;
|
|
5636
5849
|
if (DEDUCTION[sev] > prev) perRule.set(r.id, DEDUCTION[sev]);
|
|
5637
5850
|
}
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5851
|
+
let totalDeficit = 0;
|
|
5852
|
+
for (const [key, pairs] of observed) {
|
|
5853
|
+
let failed = 0;
|
|
5854
|
+
for (const d of ruleMax.get(key)?.values() ?? []) failed += d;
|
|
5855
|
+
let inventoryWeight = 0;
|
|
5856
|
+
for (const p of pairs) inventoryWeight += inventory.get(p) ?? 0;
|
|
5857
|
+
inventoryWeight = Math.max(inventoryWeight, failed);
|
|
5858
|
+
totalDeficit += inventoryWeight === 0 ? 0 : 100 * failed / inventoryWeight;
|
|
5859
|
+
}
|
|
5860
|
+
const keyCount = observed.size;
|
|
5861
|
+
const rawRouteAverage = keyCount === 0 ? 100 : 100 - totalDeficit / keyCount;
|
|
5645
5862
|
const routeAverage = Math.floor(rawRouteAverage);
|
|
5646
5863
|
const projectRuleMax = /* @__PURE__ */ new Map();
|
|
5647
5864
|
for (const r of projectResults) {
|
|
@@ -5861,9 +6078,20 @@ function issueOf(result) {
|
|
|
5861
6078
|
...result.fix ? { fix: result.fix } : {}
|
|
5862
6079
|
};
|
|
5863
6080
|
}
|
|
5864
|
-
function
|
|
6081
|
+
function ruleEvidence(results, config, ruleIds) {
|
|
6082
|
+
const out = {};
|
|
6083
|
+
for (const id of ruleIds ?? []) out[id] = { findings: 0, passed: 0 };
|
|
6084
|
+
for (const r of results) {
|
|
6085
|
+
const entry = out[r.id] ??= { findings: 0, passed: 0 };
|
|
6086
|
+
if (isPenalized(r.detection, config.treatDynamicAs)) entry.findings += 1;
|
|
6087
|
+
else entry.passed += 1;
|
|
6088
|
+
}
|
|
6089
|
+
return out;
|
|
6090
|
+
}
|
|
6091
|
+
function buildJsonReport(results, config, meta, ruleIds) {
|
|
5865
6092
|
const { health, categories: byCat, weights } = computeHealth(results, config);
|
|
5866
6093
|
const summary = summarize(results, config);
|
|
6094
|
+
const rules = ruleEvidence(results, config, ruleIds);
|
|
5867
6095
|
const categories = Object.fromEntries(
|
|
5868
6096
|
Object.entries(byCat).map(([cat, sr]) => [cat, { score: sr.score, scoreModel: sr.scoreModel }])
|
|
5869
6097
|
);
|
|
@@ -5879,10 +6107,10 @@ function buildJsonReport(results, config, meta) {
|
|
|
5879
6107
|
issues: rs.filter((r) => isPenalized(r.detection, config.treatDynamicAs)).map((r) => ({ ...issueOf(r), severity: effectiveSeverity(r, config) }))
|
|
5880
6108
|
}));
|
|
5881
6109
|
const siteIssues = results.filter((r) => r.route === void 0 && isPenalized(r.detection, config.treatDynamicAs)).map((r) => ({ ...issueOf(r), severity: effectiveSeverity(r, config) }));
|
|
5882
|
-
return { version: meta.version, score: health, weights, categories, summary, routes, siteIssues };
|
|
6110
|
+
return { version: meta.version, score: health, weights, categories, summary, rules, routes, siteIssues };
|
|
5883
6111
|
}
|
|
5884
|
-
function formatJsonReport(results, config, meta) {
|
|
5885
|
-
return JSON.stringify(buildJsonReport(results, config, meta), null, 2);
|
|
6112
|
+
function formatJsonReport(results, config, meta, ruleIds) {
|
|
6113
|
+
return JSON.stringify(buildJsonReport(results, config, meta, ruleIds), null, 2);
|
|
5886
6114
|
}
|
|
5887
6115
|
|
|
5888
6116
|
// src/reporter/agent.ts
|
|
@@ -6816,6 +7044,7 @@ export {
|
|
|
6816
7044
|
applyRuleSeverities,
|
|
6817
7045
|
architectureComponentSize,
|
|
6818
7046
|
architectureDirectoryNaming,
|
|
7047
|
+
architectureDocLinkTarget,
|
|
6819
7048
|
architecturePrivateScopeImport,
|
|
6820
7049
|
architecturePropCount,
|
|
6821
7050
|
architectureReservedDirectoryNames,
|