@svelte-vitals/core 0.32.0 → 0.34.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 +24 -4
- package/dist/index.js +176 -22
- 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. */
|
|
@@ -1266,6 +1271,8 @@ declare const architectureReservedDirectoryNames: Rule;
|
|
|
1266
1271
|
|
|
1267
1272
|
declare const architectureRouteComponentImport: Rule;
|
|
1268
1273
|
|
|
1274
|
+
declare const architectureDocLinkTarget: Rule;
|
|
1275
|
+
|
|
1269
1276
|
declare const performanceHeavyImport: Rule;
|
|
1270
1277
|
|
|
1271
1278
|
declare const performanceNamespaceImport: Rule;
|
|
@@ -1458,11 +1465,13 @@ interface ScoreResult {
|
|
|
1458
1465
|
}
|
|
1459
1466
|
interface ScoreOptions {
|
|
1460
1467
|
applyCriticalCap?: boolean;
|
|
1468
|
+
/** The rules that ran. Defaults to the selected registry; supplied by tests and custom rule sets. */
|
|
1469
|
+
rules?: readonly Rule[];
|
|
1461
1470
|
}
|
|
1462
1471
|
/** Compute the headline score and its breakdown (design §12). */
|
|
1463
1472
|
declare function computeScore(results: Result[], config: Config, options?: ScoreOptions): ScoreResult;
|
|
1464
1473
|
/** Compute an independent score per category present in `results` (issue #10). */
|
|
1465
|
-
declare function scoresByCategory(results: Result[], config: Config): Partial<Record<Category, ScoreResult>>;
|
|
1474
|
+
declare function scoresByCategory(results: Result[], config: Config, options?: ScoreOptions): Partial<Record<Category, ScoreResult>>;
|
|
1466
1475
|
interface HealthResult {
|
|
1467
1476
|
/** Weighted overall score across present categories (0–100). */
|
|
1468
1477
|
health: number;
|
|
@@ -1487,6 +1496,15 @@ declare function issueOf(result: Result): {
|
|
|
1487
1496
|
type JsonIssue = ReturnType<typeof issueOf> & {
|
|
1488
1497
|
severity: ReturnType<typeof effectiveSeverity>;
|
|
1489
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
|
+
}
|
|
1490
1508
|
interface JsonReport {
|
|
1491
1509
|
version: string;
|
|
1492
1510
|
score: number;
|
|
@@ -1496,9 +1514,11 @@ interface JsonReport {
|
|
|
1496
1514
|
scoreModel: ScoreModel;
|
|
1497
1515
|
}>;
|
|
1498
1516
|
summary: Summary;
|
|
1517
|
+
rules: Record<string, RuleEvidence>;
|
|
1499
1518
|
routes: Array<{
|
|
1500
1519
|
route: string;
|
|
1501
1520
|
score: number;
|
|
1521
|
+
categories: Record<string, number>;
|
|
1502
1522
|
issues: JsonIssue[];
|
|
1503
1523
|
}>;
|
|
1504
1524
|
siteIssues: JsonIssue[];
|
|
@@ -1506,11 +1526,11 @@ interface JsonReport {
|
|
|
1506
1526
|
/** Build the structured JSON report object (design §7). The shape the `json` reporter emits (issue #24). */
|
|
1507
1527
|
declare function buildJsonReport(results: Result[], config: Config, meta: {
|
|
1508
1528
|
version: string;
|
|
1509
|
-
}): JsonReport;
|
|
1529
|
+
}, ruleIds?: readonly string[]): JsonReport;
|
|
1510
1530
|
/** Render results as the documented JSON report string (design §7). */
|
|
1511
1531
|
declare function formatJsonReport(results: Result[], config: Config, meta: {
|
|
1512
1532
|
version: string;
|
|
1513
|
-
}): string;
|
|
1533
|
+
}, ruleIds?: readonly string[]): string;
|
|
1514
1534
|
|
|
1515
1535
|
/** Render failing findings as an agent-actionable Markdown remediation document (issue #18). */
|
|
1516
1536
|
declare function formatAgentReport(results: Result[], config: Config): string;
|
|
@@ -1602,4 +1622,4 @@ declare function escapeHtml(s: string): string;
|
|
|
1602
1622
|
*/
|
|
1603
1623
|
declare function safeHref(url: string): string | null;
|
|
1604
1624
|
|
|
1605
|
-
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 };
|
|
1625
|
+
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) {
|
|
@@ -5416,6 +5466,60 @@ var architectureRouteComponentImport = componentRule({
|
|
|
5416
5466
|
}
|
|
5417
5467
|
});
|
|
5418
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
|
+
|
|
5419
5523
|
// src/rules/perf/heavy-import.ts
|
|
5420
5524
|
var HEAVY_PACKAGES = {
|
|
5421
5525
|
lodash: "import a submodule (lodash/debounce) or use lodash-es for tree-shaking",
|
|
@@ -5632,6 +5736,7 @@ var allRules = [
|
|
|
5632
5736
|
architectureDirectoryNaming,
|
|
5633
5737
|
architectureReservedDirectoryNames,
|
|
5634
5738
|
architectureRouteComponentImport,
|
|
5739
|
+
architectureDocLinkTarget,
|
|
5635
5740
|
performanceHeavyImport,
|
|
5636
5741
|
performanceNamespaceImport,
|
|
5637
5742
|
performanceMinifyDisabled,
|
|
@@ -5692,8 +5797,31 @@ function hasFailureAtOrAbove(summary, min) {
|
|
|
5692
5797
|
return order.some((sev, idx) => idx >= threshold && summary[sev] > 0);
|
|
5693
5798
|
}
|
|
5694
5799
|
|
|
5695
|
-
// src/scoring/
|
|
5800
|
+
// src/scoring/inventory.ts
|
|
5696
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
|
|
5697
5825
|
var CRITICAL_CAP = 79;
|
|
5698
5826
|
function clamp(n) {
|
|
5699
5827
|
return Math.max(0, Math.min(100, n));
|
|
@@ -5701,27 +5829,36 @@ function clamp(n) {
|
|
|
5701
5829
|
function computeScore(results, config, options = {}) {
|
|
5702
5830
|
const routeResults = results.filter((r) => r.route !== void 0);
|
|
5703
5831
|
const projectResults = results.filter((r) => r.route === void 0);
|
|
5704
|
-
const
|
|
5705
|
-
|
|
5832
|
+
const rules = selectRules([...options.rules ?? allRules], config);
|
|
5833
|
+
const inventory = buildInventory(config, rules);
|
|
5834
|
+
const pairOf = ruleScopes(rules);
|
|
5706
5835
|
let anyCritical = false;
|
|
5707
|
-
const
|
|
5836
|
+
const observed = /* @__PURE__ */ new Map();
|
|
5837
|
+
const ruleMax = /* @__PURE__ */ new Map();
|
|
5708
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);
|
|
5709
5843
|
if (!isPenalized(r.detection, config.treatDynamicAs)) continue;
|
|
5710
5844
|
const sev = effectiveSeverity(r, config);
|
|
5711
5845
|
if (sev === "critical") anyCritical = true;
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
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());
|
|
5715
5848
|
const prev = perRule.get(r.id) ?? 0;
|
|
5716
5849
|
if (DEDUCTION[sev] > prev) perRule.set(r.id, DEDUCTION[sev]);
|
|
5717
5850
|
}
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
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;
|
|
5725
5862
|
const routeAverage = Math.floor(rawRouteAverage);
|
|
5726
5863
|
const projectRuleMax = /* @__PURE__ */ new Map();
|
|
5727
5864
|
for (const r of projectResults) {
|
|
@@ -5740,7 +5877,7 @@ function computeScore(results, config, options = {}) {
|
|
|
5740
5877
|
const rawScore = clamp(capBinds ? CRITICAL_CAP : rawUncapped);
|
|
5741
5878
|
return { score: Math.floor(rawScore), rawScore, scoreModel: { routeAverage, sitePenalty, criticalCap } };
|
|
5742
5879
|
}
|
|
5743
|
-
function scoresByCategory(results, config) {
|
|
5880
|
+
function scoresByCategory(results, config, options = {}) {
|
|
5744
5881
|
const byCat = /* @__PURE__ */ new Map();
|
|
5745
5882
|
for (const r of results) {
|
|
5746
5883
|
const cat = r.category ?? "seo";
|
|
@@ -5749,7 +5886,7 @@ function scoresByCategory(results, config) {
|
|
|
5749
5886
|
bucket.push(r);
|
|
5750
5887
|
}
|
|
5751
5888
|
const out = {};
|
|
5752
|
-
for (const [cat, rs] of byCat) out[cat] = computeScore(rs, config);
|
|
5889
|
+
for (const [cat, rs] of byCat) out[cat] = computeScore(rs, config, options);
|
|
5753
5890
|
return out;
|
|
5754
5891
|
}
|
|
5755
5892
|
function computeHealth(results, config) {
|
|
@@ -5941,9 +6078,20 @@ function issueOf(result) {
|
|
|
5941
6078
|
...result.fix ? { fix: result.fix } : {}
|
|
5942
6079
|
};
|
|
5943
6080
|
}
|
|
5944
|
-
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) {
|
|
5945
6092
|
const { health, categories: byCat, weights } = computeHealth(results, config);
|
|
5946
6093
|
const summary = summarize(results, config);
|
|
6094
|
+
const rules = ruleEvidence(results, config, ruleIds);
|
|
5947
6095
|
const categories = Object.fromEntries(
|
|
5948
6096
|
Object.entries(byCat).map(([cat, sr]) => [cat, { score: sr.score, scoreModel: sr.scoreModel }])
|
|
5949
6097
|
);
|
|
@@ -5956,13 +6104,18 @@ function buildJsonReport(results, config, meta) {
|
|
|
5956
6104
|
const routes = [...routeMap.values()].sort((a, b) => a.route.localeCompare(b.route)).map(({ route, results: rs }) => ({
|
|
5957
6105
|
route,
|
|
5958
6106
|
score: computeScore(rs, config, { applyCriticalCap: false }).score,
|
|
6107
|
+
// Per category, scored against that category's own inventory, so this is not guaranteed to average to
|
|
6108
|
+
// `score` — which is one ratio over the union of the pairs the route touched.
|
|
6109
|
+
categories: Object.fromEntries(
|
|
6110
|
+
Object.entries(scoresByCategory(rs, config, { applyCriticalCap: false })).map(([cat, sr]) => [cat, sr.score])
|
|
6111
|
+
),
|
|
5959
6112
|
issues: rs.filter((r) => isPenalized(r.detection, config.treatDynamicAs)).map((r) => ({ ...issueOf(r), severity: effectiveSeverity(r, config) }))
|
|
5960
6113
|
}));
|
|
5961
6114
|
const siteIssues = results.filter((r) => r.route === void 0 && isPenalized(r.detection, config.treatDynamicAs)).map((r) => ({ ...issueOf(r), severity: effectiveSeverity(r, config) }));
|
|
5962
|
-
return { version: meta.version, score: health, weights, categories, summary, routes, siteIssues };
|
|
6115
|
+
return { version: meta.version, score: health, weights, categories, summary, rules, routes, siteIssues };
|
|
5963
6116
|
}
|
|
5964
|
-
function formatJsonReport(results, config, meta) {
|
|
5965
|
-
return JSON.stringify(buildJsonReport(results, config, meta), null, 2);
|
|
6117
|
+
function formatJsonReport(results, config, meta, ruleIds) {
|
|
6118
|
+
return JSON.stringify(buildJsonReport(results, config, meta, ruleIds), null, 2);
|
|
5966
6119
|
}
|
|
5967
6120
|
|
|
5968
6121
|
// src/reporter/agent.ts
|
|
@@ -6896,6 +7049,7 @@ export {
|
|
|
6896
7049
|
applyRuleSeverities,
|
|
6897
7050
|
architectureComponentSize,
|
|
6898
7051
|
architectureDirectoryNaming,
|
|
7052
|
+
architectureDocLinkTarget,
|
|
6899
7053
|
architecturePrivateScopeImport,
|
|
6900
7054
|
architecturePropCount,
|
|
6901
7055
|
architectureReservedDirectoryNames,
|