@svelte-vitals/core 0.7.0 → 0.8.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 +54 -2
- package/dist/index.js +146 -17
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -47,6 +47,10 @@ interface Result {
|
|
|
47
47
|
docsUrl?: string;
|
|
48
48
|
/** Agent-actionable remediation (issue #18). */
|
|
49
49
|
fix?: Fix;
|
|
50
|
+
/** Vitals category this finding belongs to (default 'seo' when absent). */
|
|
51
|
+
category?: Category;
|
|
52
|
+
/** 1-based source line for element-level findings (e.g. a specific <img>). */
|
|
53
|
+
line?: number;
|
|
50
54
|
}
|
|
51
55
|
type Scope = 'route' | 'project';
|
|
52
56
|
type Category = 'seo' | 'performance' | 'a11y' | 'maintainability';
|
|
@@ -127,6 +131,26 @@ interface HeadProvider {
|
|
|
127
131
|
collect(rt: Runtime, cwd: string, config?: Config): Promise<ResolvedHead[]>;
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
/**
|
|
135
|
+
* A normalized <img> occurrence — the mode-independent boundary for Performance
|
|
136
|
+
* rules (mirrors head.ts). Attribute presence only: a dynamically-bound attribute
|
|
137
|
+
* (width={w}) still counts as present, so dynamic values are never flagged.
|
|
138
|
+
*/
|
|
139
|
+
interface ImageInfo {
|
|
140
|
+
hasWidth: boolean;
|
|
141
|
+
hasHeight: boolean;
|
|
142
|
+
hasLoading: boolean;
|
|
143
|
+
/** 1-based source line, or 0 if unknown. */
|
|
144
|
+
line: number;
|
|
145
|
+
/** Source file the <img> came from. */
|
|
146
|
+
file: string;
|
|
147
|
+
}
|
|
148
|
+
/** Resolved <img> elements for a single route (page + layout chain). */
|
|
149
|
+
interface ResolvedImages {
|
|
150
|
+
route: string;
|
|
151
|
+
images: ImageInfo[];
|
|
152
|
+
}
|
|
153
|
+
|
|
130
154
|
/**
|
|
131
155
|
* Source-file locations that satisfy the project-scope rules, shared by every
|
|
132
156
|
* mode so the static (CLI) and rendered (plugin) collectors never drift. This
|
|
@@ -140,6 +164,8 @@ declare const SITEMAP_SOURCE_PATHS: readonly ["static/sitemap.xml", "src/routes/
|
|
|
140
164
|
/** Input given to every rule. Mode-independent: rules see only ResolvedHead[] (design §8, §10). */
|
|
141
165
|
interface RuleContext {
|
|
142
166
|
heads: ResolvedHead[];
|
|
167
|
+
/** Per-route <img> elements for Performance rules (absent in modes that don't collect them). */
|
|
168
|
+
images?: ResolvedImages[];
|
|
143
169
|
project: Project;
|
|
144
170
|
config: Config;
|
|
145
171
|
}
|
|
@@ -198,6 +224,9 @@ declare const seo006Robots: Rule;
|
|
|
198
224
|
declare const seo007Sitemap: Rule;
|
|
199
225
|
declare const seo009HtmlLang: Rule;
|
|
200
226
|
|
|
227
|
+
declare const perf001ImageDimensions: Rule;
|
|
228
|
+
declare const perf002ImageLoading: Rule;
|
|
229
|
+
|
|
201
230
|
declare const allRules: Rule[];
|
|
202
231
|
|
|
203
232
|
interface RuleInfo {
|
|
@@ -229,6 +258,21 @@ interface HeadTagRuleOptions {
|
|
|
229
258
|
/** Build a route-scope rule asserting the presence of a single head tag (design §11). */
|
|
230
259
|
declare function headTagRule(opts: HeadTagRuleOptions): Rule;
|
|
231
260
|
|
|
261
|
+
interface ImageRuleOptions {
|
|
262
|
+
id: string;
|
|
263
|
+
title: string;
|
|
264
|
+
severity: Severity;
|
|
265
|
+
/** Noun phrase for messages, e.g. '<img> width/height'. */
|
|
266
|
+
label: string;
|
|
267
|
+
recommendation: string;
|
|
268
|
+
rationale: string;
|
|
269
|
+
fix?: Fix;
|
|
270
|
+
/** Returns true when the image satisfies the rule (passes). */
|
|
271
|
+
ok: (img: ImageInfo) => boolean;
|
|
272
|
+
}
|
|
273
|
+
/** Build a route-scoped Performance rule that checks each <img> against `ok` (issue #10). */
|
|
274
|
+
declare function imageRule(opts: ImageRuleOptions): Rule;
|
|
275
|
+
|
|
232
276
|
interface Summary {
|
|
233
277
|
critical: number;
|
|
234
278
|
warning: number;
|
|
@@ -274,15 +318,19 @@ interface ScoreOptions {
|
|
|
274
318
|
}
|
|
275
319
|
/** Compute the headline score and its breakdown (design §12). */
|
|
276
320
|
declare function computeScore(results: Result[], config: Config, options?: ScoreOptions): ScoreResult;
|
|
321
|
+
/** Compute an independent score per category present in `results` (issue #10). */
|
|
322
|
+
declare function scoresByCategory(results: Result[], config: Config): Partial<Record<Category, ScoreResult>>;
|
|
277
323
|
|
|
278
324
|
declare function issueOf(result: Result): {
|
|
279
325
|
fix?: Fix | undefined;
|
|
280
326
|
docsUrl?: string | undefined;
|
|
327
|
+
recommendation: string | undefined;
|
|
328
|
+
line?: number | undefined;
|
|
281
329
|
id: string;
|
|
330
|
+
category: Category;
|
|
282
331
|
title: string;
|
|
283
332
|
detection: Detection;
|
|
284
333
|
location: string | undefined;
|
|
285
|
-
recommendation: string | undefined;
|
|
286
334
|
};
|
|
287
335
|
type JsonIssue = ReturnType<typeof issueOf> & {
|
|
288
336
|
severity: ReturnType<typeof effectiveSeverity>;
|
|
@@ -291,6 +339,10 @@ interface JsonReport {
|
|
|
291
339
|
version: string;
|
|
292
340
|
score: number;
|
|
293
341
|
scoreModel: ScoreModel;
|
|
342
|
+
categories: Record<string, {
|
|
343
|
+
score: number;
|
|
344
|
+
scoreModel: ScoreModel;
|
|
345
|
+
}>;
|
|
294
346
|
summary: Summary;
|
|
295
347
|
routes: Array<{
|
|
296
348
|
route: string;
|
|
@@ -327,4 +379,4 @@ declare function selectRules(rules: Rule[], config: Config): Rule[];
|
|
|
327
379
|
/** Apply per-rule severity overrides to results (design §6). */
|
|
328
380
|
declare function applyRuleSeverities(results: Result[], config: Config): Result[];
|
|
329
381
|
|
|
330
|
-
export { type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type Fix, type HeadProvider, type HeadTag, type JsonReport, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type Result, type Rule, type RuleContext, type RuleInfo, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type Summary, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, buildJsonReport, classify, computeScore, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, explainRule, formatAgentReport, formatConsoleReport, formatGithubReport, formatJsonReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, isPenalized, runRules, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, summarize };
|
|
382
|
+
export { type Category, type Classification, type Config, type ConsoleReportOptions, type Detection, type Fix, type HeadProvider, type HeadTag, type ImageInfo, type JsonReport, type Presence, type Project, ROBOTS_SOURCE_PATHS, type ResolvedHead, type ResolvedImages, type Result, type Rule, type RuleContext, type RuleInfo, type RuleSetting, type Runtime, SITEMAP_SOURCE_PATHS, type Scope, type ScoreModel, type ScoreOptions, type ScoreResult, type Severity, type Summary, type TreatDynamicAs, type Value, allRules, applyRuleSeverities, buildJsonReport, classify, computeScore, defaultConfig, defaultProject, defineConfig, docsUrlFor, effectiveSeverity, explainRule, formatAgentReport, formatConsoleReport, formatGithubReport, formatJsonReport, formatSarifReport, hasFailureAtOrAbove, headTagRule, imageRule, isPenalized, perf001ImageDimensions, perf002ImageLoading, runRules, scoresByCategory, selectRules, seo001Title, seo002Description, seo003Canonical, seo004OgImage, seo005OgTitle, seo006Robots, seo007Sitemap, seo008JsonLd, seo009HtmlLang, summarize };
|
package/dist/index.js
CHANGED
|
@@ -74,6 +74,7 @@ var seo001Title = {
|
|
|
74
74
|
const detection = detectTitle(head);
|
|
75
75
|
return {
|
|
76
76
|
id: "SEO001",
|
|
77
|
+
category: "seo",
|
|
77
78
|
severity: "critical",
|
|
78
79
|
detection,
|
|
79
80
|
route: head.route,
|
|
@@ -108,6 +109,7 @@ function headTagRule(opts) {
|
|
|
108
109
|
const message = detection.presence === "none" ? `Missing ${opts.label}` : detection.value === "absent" ? `Empty ${opts.label}` : opts.label;
|
|
109
110
|
return {
|
|
110
111
|
id: opts.id,
|
|
112
|
+
category: "seo",
|
|
111
113
|
severity: opts.severity,
|
|
112
114
|
detection,
|
|
113
115
|
route: head.route,
|
|
@@ -220,6 +222,7 @@ var seo006Robots = {
|
|
|
220
222
|
return [
|
|
221
223
|
{
|
|
222
224
|
id: "SEO006",
|
|
225
|
+
category: "seo",
|
|
223
226
|
severity: "warning",
|
|
224
227
|
detection,
|
|
225
228
|
message: ctx.project.hasRobotsTxt ? "robots.txt" : "Missing robots.txt",
|
|
@@ -248,6 +251,7 @@ var seo007Sitemap = {
|
|
|
248
251
|
return [
|
|
249
252
|
{
|
|
250
253
|
id: "SEO007",
|
|
254
|
+
category: "seo",
|
|
251
255
|
severity: "warning",
|
|
252
256
|
detection,
|
|
253
257
|
message: ctx.project.hasSitemap ? "sitemap.xml" : "Missing sitemap.xml",
|
|
@@ -277,6 +281,7 @@ var seo009HtmlLang = {
|
|
|
277
281
|
return [
|
|
278
282
|
{
|
|
279
283
|
id: "SEO009",
|
|
284
|
+
category: "seo",
|
|
280
285
|
severity: "warning",
|
|
281
286
|
detection,
|
|
282
287
|
message,
|
|
@@ -288,6 +293,86 @@ var seo009HtmlLang = {
|
|
|
288
293
|
}
|
|
289
294
|
};
|
|
290
295
|
|
|
296
|
+
// src/rules/perf/image-rule.ts
|
|
297
|
+
function imageRule(opts) {
|
|
298
|
+
const docsUrl = docsUrlFor(opts.id);
|
|
299
|
+
return {
|
|
300
|
+
id: opts.id,
|
|
301
|
+
title: opts.title,
|
|
302
|
+
category: "performance",
|
|
303
|
+
severity: opts.severity,
|
|
304
|
+
scope: "route",
|
|
305
|
+
rationale: opts.rationale,
|
|
306
|
+
...opts.fix ? { fix: opts.fix } : {},
|
|
307
|
+
async check(ctx) {
|
|
308
|
+
const out = [];
|
|
309
|
+
for (const route of ctx.images ?? []) {
|
|
310
|
+
if (route.images.length === 0) continue;
|
|
311
|
+
const bad = route.images.filter((img) => !opts.ok(img));
|
|
312
|
+
if (bad.length === 0) {
|
|
313
|
+
out.push({
|
|
314
|
+
id: opts.id,
|
|
315
|
+
category: "performance",
|
|
316
|
+
severity: opts.severity,
|
|
317
|
+
detection: { presence: "own", value: "static" },
|
|
318
|
+
route: route.route,
|
|
319
|
+
message: opts.label,
|
|
320
|
+
recommendation: opts.recommendation,
|
|
321
|
+
docsUrl
|
|
322
|
+
});
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
for (const img of bad) {
|
|
326
|
+
out.push({
|
|
327
|
+
id: opts.id,
|
|
328
|
+
category: "performance",
|
|
329
|
+
severity: opts.severity,
|
|
330
|
+
detection: { presence: "none", value: "absent" },
|
|
331
|
+
route: route.route,
|
|
332
|
+
location: img.file,
|
|
333
|
+
...img.line > 0 ? { line: img.line } : {},
|
|
334
|
+
message: `Missing ${opts.label}`,
|
|
335
|
+
recommendation: opts.recommendation,
|
|
336
|
+
docsUrl,
|
|
337
|
+
...opts.fix ? { fix: { ...opts.fix } } : {}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/rules/perf/images.ts
|
|
347
|
+
var perf001ImageDimensions = imageRule({
|
|
348
|
+
id: "PERF001",
|
|
349
|
+
title: "Image dimensions",
|
|
350
|
+
severity: "warning",
|
|
351
|
+
label: "<img> width/height",
|
|
352
|
+
recommendation: "Set explicit width and height on <img> to reserve space and avoid layout shift (CLS).",
|
|
353
|
+
rationale: "An <img> without explicit width and height triggers layout shift (CLS) as it loads, hurting Core Web Vitals and visual stability.",
|
|
354
|
+
fix: {
|
|
355
|
+
description: "Add explicit width and height attributes to the <img>.",
|
|
356
|
+
snippet: '<img src="/hero.jpg" width="1200" height="630" alt="\u2026" />',
|
|
357
|
+
lang: "svelte"
|
|
358
|
+
},
|
|
359
|
+
ok: (img) => img.hasWidth && img.hasHeight
|
|
360
|
+
});
|
|
361
|
+
var perf002ImageLoading = imageRule({
|
|
362
|
+
id: "PERF002",
|
|
363
|
+
title: "Image loading hint",
|
|
364
|
+
severity: "info",
|
|
365
|
+
label: "<img> loading attribute",
|
|
366
|
+
recommendation: 'Set loading="lazy" for offscreen images; keep the LCP image eager (consider fetchpriority="high").',
|
|
367
|
+
rationale: "A loading attribute lets the browser defer offscreen images; without it images load eagerly and can delay more important content. Static analysis cannot tell which image is the LCP, so this is advisory.",
|
|
368
|
+
fix: {
|
|
369
|
+
description: 'Add loading="lazy" to offscreen <img> elements (leave the LCP/hero image eager).',
|
|
370
|
+
snippet: '<img src="/thumb.jpg" width="320" height="240" loading="lazy" alt="\u2026" />',
|
|
371
|
+
lang: "svelte"
|
|
372
|
+
},
|
|
373
|
+
ok: (img) => img.hasLoading
|
|
374
|
+
});
|
|
375
|
+
|
|
291
376
|
// src/rules/index.ts
|
|
292
377
|
var allRules = [
|
|
293
378
|
seo001Title,
|
|
@@ -298,7 +383,9 @@ var allRules = [
|
|
|
298
383
|
seo006Robots,
|
|
299
384
|
seo007Sitemap,
|
|
300
385
|
seo008JsonLd,
|
|
301
|
-
seo009HtmlLang
|
|
386
|
+
seo009HtmlLang,
|
|
387
|
+
perf001ImageDimensions,
|
|
388
|
+
perf002ImageLoading
|
|
302
389
|
];
|
|
303
390
|
function explainRule(id) {
|
|
304
391
|
const target = id.toUpperCase();
|
|
@@ -391,6 +478,18 @@ function computeScore(results, config, options = {}) {
|
|
|
391
478
|
const score = capBinds ? CRITICAL_CAP : uncapped;
|
|
392
479
|
return { score: clamp(score), scoreModel: { routeAverage, sitePenalty, criticalCap } };
|
|
393
480
|
}
|
|
481
|
+
function scoresByCategory(results, config) {
|
|
482
|
+
const byCat = /* @__PURE__ */ new Map();
|
|
483
|
+
for (const r of results) {
|
|
484
|
+
const cat = r.category ?? "seo";
|
|
485
|
+
let bucket = byCat.get(cat);
|
|
486
|
+
if (!bucket) byCat.set(cat, bucket = []);
|
|
487
|
+
bucket.push(r);
|
|
488
|
+
}
|
|
489
|
+
const out = {};
|
|
490
|
+
for (const [cat, rs] of byCat) out[cat] = computeScore(rs, config);
|
|
491
|
+
return out;
|
|
492
|
+
}
|
|
394
493
|
|
|
395
494
|
// src/reporter/console.ts
|
|
396
495
|
var RULE = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
|
|
@@ -399,12 +498,18 @@ var SEVERITY_TITLE = {
|
|
|
399
498
|
warning: "Warnings",
|
|
400
499
|
info: "Info"
|
|
401
500
|
};
|
|
402
|
-
|
|
403
|
-
|
|
501
|
+
var CATEGORY_LABEL = {
|
|
502
|
+
seo: "SEO",
|
|
503
|
+
performance: "Performance",
|
|
504
|
+
a11y: "Accessibility",
|
|
505
|
+
maintainability: "Maintainability"
|
|
506
|
+
};
|
|
507
|
+
var CATEGORY_ORDER = ["seo", "performance", "a11y", "maintainability"];
|
|
508
|
+
function scoreLine(label, { score, scoreModel }) {
|
|
404
509
|
const parts = [`route avg ${scoreModel.routeAverage}`];
|
|
405
510
|
if (scoreModel.sitePenalty > 0) parts.push(`site \u2212${scoreModel.sitePenalty}`);
|
|
406
511
|
if (scoreModel.criticalCap !== null) parts.push(`capped at ${scoreModel.criticalCap}: critical present`);
|
|
407
|
-
return
|
|
512
|
+
return `${label} Score: ${score}/100 (${parts.join(" \xB7 ")})`;
|
|
408
513
|
}
|
|
409
514
|
function byRouteTree(results, config) {
|
|
410
515
|
const routes = /* @__PURE__ */ new Map();
|
|
@@ -426,12 +531,13 @@ function byRouteTree(results, config) {
|
|
|
426
531
|
}
|
|
427
532
|
function formatConsoleReport(results, config, options = {}) {
|
|
428
533
|
const summary = summarize(results, config);
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
534
|
+
const byCat = scoresByCategory(results, config);
|
|
535
|
+
const present2 = CATEGORY_ORDER.filter((c) => byCat[c] !== void 0);
|
|
536
|
+
const header = [`Svelte Vitals \xB7 ${options.mode ?? "static mode"}`, ""];
|
|
537
|
+
for (const c of present2) {
|
|
538
|
+
header.push(scoreLine(CATEGORY_LABEL[c] ?? c, byCat[c]));
|
|
539
|
+
}
|
|
540
|
+
const lines = [...header, ""];
|
|
435
541
|
const failures = results.filter((r) => classify(r, config) === "fail");
|
|
436
542
|
for (const severity of ["critical", "warning", "info"]) {
|
|
437
543
|
const bucket = failures.filter((r) => effectiveSeverity(r, config) === severity);
|
|
@@ -440,7 +546,7 @@ function formatConsoleReport(results, config, options = {}) {
|
|
|
440
546
|
for (const r of bucket) {
|
|
441
547
|
lines.push(`\u2717 ${r.id} ${r.message}`);
|
|
442
548
|
if (r.route) lines.push(` ${r.route}`);
|
|
443
|
-
if (r.location) lines.push(` ${r.location}`);
|
|
549
|
+
if (r.location) lines.push(` ${r.location}${r.line ? `:${r.line}` : ""}`);
|
|
444
550
|
}
|
|
445
551
|
lines.push("");
|
|
446
552
|
}
|
|
@@ -463,17 +569,24 @@ function formatConsoleReport(results, config, options = {}) {
|
|
|
463
569
|
function issueOf(result) {
|
|
464
570
|
return {
|
|
465
571
|
id: result.id,
|
|
572
|
+
category: result.category ?? "seo",
|
|
466
573
|
title: result.message,
|
|
467
574
|
detection: result.detection,
|
|
468
575
|
location: result.location,
|
|
576
|
+
...result.line !== void 0 ? { line: result.line } : {},
|
|
469
577
|
recommendation: result.recommendation,
|
|
470
578
|
...result.docsUrl ? { docsUrl: result.docsUrl } : {},
|
|
471
579
|
...result.fix ? { fix: result.fix } : {}
|
|
472
580
|
};
|
|
473
581
|
}
|
|
474
582
|
function buildJsonReport(results, config, meta) {
|
|
475
|
-
const
|
|
583
|
+
const seoResults = results.filter((r) => (r.category ?? "seo") === "seo");
|
|
584
|
+
const { score, scoreModel } = computeScore(seoResults, config);
|
|
476
585
|
const summary = summarize(results, config);
|
|
586
|
+
const byCat = scoresByCategory(results, config);
|
|
587
|
+
const categories = Object.fromEntries(
|
|
588
|
+
Object.entries(byCat).map(([cat, sr]) => [cat, { score: sr.score, scoreModel: sr.scoreModel }])
|
|
589
|
+
);
|
|
477
590
|
const routeMap = /* @__PURE__ */ new Map();
|
|
478
591
|
for (const r of results) {
|
|
479
592
|
if (r.route === void 0) continue;
|
|
@@ -486,7 +599,7 @@ function buildJsonReport(results, config, meta) {
|
|
|
486
599
|
issues: rs.filter((r) => isPenalized(r.detection, config.treatDynamicAs)).map((r) => ({ ...issueOf(r), severity: effectiveSeverity(r, config) }))
|
|
487
600
|
}));
|
|
488
601
|
const siteIssues = results.filter((r) => r.route === void 0 && isPenalized(r.detection, config.treatDynamicAs)).map((r) => ({ ...issueOf(r), severity: effectiveSeverity(r, config) }));
|
|
489
|
-
return { version: meta.version, score, scoreModel, summary, routes, siteIssues };
|
|
602
|
+
return { version: meta.version, score, scoreModel, categories, summary, routes, siteIssues };
|
|
490
603
|
}
|
|
491
604
|
function formatJsonReport(results, config, meta) {
|
|
492
605
|
return JSON.stringify(buildJsonReport(results, config, meta), null, 2);
|
|
@@ -499,7 +612,7 @@ function mdTags(text) {
|
|
|
499
612
|
}
|
|
500
613
|
function formatAgentReport(results, config) {
|
|
501
614
|
const failing = results.filter((r) => classify(r, config) === "fail");
|
|
502
|
-
const lines = ["# svelte-vitals \u2014
|
|
615
|
+
const lines = ["# svelte-vitals \u2014 fixes", ""];
|
|
503
616
|
if (failing.length === 0) {
|
|
504
617
|
lines.push("No issues to fix.", "");
|
|
505
618
|
return lines.join("\n").replace(/\n+$/, "\n");
|
|
@@ -581,10 +694,19 @@ function formatSarifReport(results, config, meta) {
|
|
|
581
694
|
ruleIndex: ruleIndex.get(r.id),
|
|
582
695
|
level: severityToSarifLevel(effectiveSeverity(r, config)),
|
|
583
696
|
message: { text: messageText(r) },
|
|
584
|
-
partialFingerprints: {
|
|
697
|
+
partialFingerprints: {
|
|
698
|
+
"svelteVitals/v1": `${r.id}:${r.route ?? "project"}${r.line !== void 0 ? `:${r.location ?? ""}:${r.line}` : ""}`
|
|
699
|
+
}
|
|
585
700
|
};
|
|
586
701
|
if (r.location) {
|
|
587
|
-
result.locations = [
|
|
702
|
+
result.locations = [
|
|
703
|
+
{
|
|
704
|
+
physicalLocation: {
|
|
705
|
+
artifactLocation: { uri: r.location },
|
|
706
|
+
...r.line !== void 0 ? { region: { startLine: r.line } } : {}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
];
|
|
588
710
|
}
|
|
589
711
|
return result;
|
|
590
712
|
});
|
|
@@ -622,7 +744,10 @@ function formatGithubReport(results, config) {
|
|
|
622
744
|
const meta = ruleMetaById(r.id);
|
|
623
745
|
const title = meta ? `${r.id}: ${meta.title}` : r.id;
|
|
624
746
|
const props = [];
|
|
625
|
-
if (r.location)
|
|
747
|
+
if (r.location) {
|
|
748
|
+
props.push(`file=${escapeProp(r.location)}`);
|
|
749
|
+
if (r.line !== void 0) props.push(`line=${r.line}`);
|
|
750
|
+
}
|
|
626
751
|
props.push(`title=${escapeProp(title)}`);
|
|
627
752
|
return `::${level} ${props.join(",")}::${escapeData(messageText(r))}`;
|
|
628
753
|
});
|
|
@@ -660,8 +785,12 @@ export {
|
|
|
660
785
|
formatSarifReport,
|
|
661
786
|
hasFailureAtOrAbove,
|
|
662
787
|
headTagRule,
|
|
788
|
+
imageRule,
|
|
663
789
|
isPenalized,
|
|
790
|
+
perf001ImageDimensions,
|
|
791
|
+
perf002ImageLoading,
|
|
664
792
|
runRules,
|
|
793
|
+
scoresByCategory,
|
|
665
794
|
selectRules,
|
|
666
795
|
seo001Title,
|
|
667
796
|
seo002Description,
|