Skip to content

Commit 8d2de1b

Browse files
committed
feat(w3c/headers): validate header links
1 parent fca26bd commit 8d2de1b

File tree

3 files changed

+73
-39
lines changed

3 files changed

+73
-39
lines changed

src/w3c/headers.js

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -393,11 +393,22 @@ export async function run(conf) {
393393
}
394394

395395
if (conf.isEd) conf.thisVersion = conf.edDraftURI;
396-
if (conf.isCGBG) validateCGBG(conf);
396+
if (conf.isCGBG) {
397+
validateCGBG(conf);
398+
}
399+
if (conf.isTagEditorFinding && !conf.latestVersion) {
400+
conf.latestVersion = null;
401+
}
397402
if (conf.latestVersion !== null) {
398403
conf.latestVersion = conf.latestVersion
399404
? w3Url(conf.latestVersion)
400405
: w3Url(`${pubSpace}/${conf.shortName}/`);
406+
const exists = await resourceExists(conf.latestVersion);
407+
if (!exists && conf.specStatus !== "FPWD") {
408+
const msg = `The "Latest published version:" header link points to a URL that does not exist.`;
409+
const hint = docLink`Check that the ${"[shortname]"} is correct and you are using the right ${"[specStatus]"} for this kind of document.`;
410+
showWarning(msg, name, { hint });
411+
}
401412
}
402413

403414
if (conf.latestVersion) validateIfAllowedOnTR(conf);
@@ -474,7 +485,9 @@ export async function run(conf) {
474485
conf.publishISODate = conf.publishDate.toISOString();
475486
conf.shortISODate = ISODate.format(conf.publishDate);
476487
validatePatentPolicies(conf);
477-
await deriveHistoryURI(conf);
488+
489+
conf.historyURI = await deriveHistoryURI(conf);
490+
478491
if (conf.isTagEditorFinding) {
479492
delete conf.thisVersion;
480493
delete conf.latestVersion;
@@ -668,7 +681,6 @@ function validateIfAllowedOnTR(conf) {
668681
const msg = docLink`Documents with a status of \`"${conf.specStatus}"\` can't be published on the W3C's /TR/ (Technical Report) space.`;
669682
const hint = docLink`Ask a W3C Team Member for a W3C URL where the report can be published and change ${"[latestVersion]"} to something else.`;
670683
showError(msg, name, { hint });
671-
return;
672684
}
673685
}
674686

@@ -694,18 +706,23 @@ function derivePubSpace(conf) {
694706

695707
function validateCGBG(conf) {
696708
const reportType = status2text[conf.specStatus];
697-
const latestVersionURL = conf.latestVersion
698-
? new URL(w3Url(conf.latestVersion))
699-
: null;
700709

701710
if (!conf.wg) {
702711
const msg = docLink`The ${"[group]"} configuration option is required for this kind of document (${reportType}).`;
703712
showError(msg, name);
704713
return;
705714
}
706715

716+
if (conf.specStatus.endsWith("-DRAFT") && !conf.latestVersion) {
717+
conf.latestVersion = null;
718+
return;
719+
}
720+
707721
// Deal with final reports
708722
if (conf.isCGFinal) {
723+
const latestVersionURL = conf.latestVersion
724+
? new URL(w3Url(conf.latestVersion))
725+
: null;
709726
// Final report require a w3.org URL.
710727
const isW3C =
711728
latestVersionURL?.origin === "https://www.w3.org" ||
@@ -720,18 +737,17 @@ function validateCGBG(conf) {
720737
}
721738

722739
async function deriveHistoryURI(conf) {
723-
if (!conf.shortName || conf.historyURI === null || !conf.latestVersion) {
724-
return; // Nothing to do
740+
if (!conf.shortName || !conf.latestVersion) {
741+
return null;
725742
}
726743

727744
const canShowHistory = conf.isEd || trStatus.includes(conf.specStatus);
728745

729-
if (conf.historyURI && !canShowHistory) {
730-
const msg = docLink`The ${"[historyURI]"} can't be used with non /TR/ documents.`;
746+
if (!canShowHistory && conf.historyURI) {
747+
const msg = docLink`The ${"[historyURI]"} can't be used with non-standards track documents.`;
731748
const hint = docLink`Please remove ${"[historyURI]"}.`;
732749
showError(msg, name, { hint });
733-
conf.historyURI = null;
734-
return;
750+
return conf.historyURI;
735751
}
736752

737753
const historyURL = new URL(
@@ -753,13 +769,16 @@ async function deriveHistoryURI(conf) {
753769
// Do a fetch HEAD request to see if the history exists...
754770
// We don't discriminate... if it's on the W3C website with a history,
755771
// we show it.
772+
const exists = await resourceExists(historyURL);
773+
return exists ? historyURL.href : null;
774+
}
775+
776+
async function resourceExists(url) {
756777
try {
757-
const response = await fetch(historyURL, { method: "HEAD" });
758-
if (response.ok) {
759-
conf.historyURI = response.url;
760-
}
778+
const response = await fetch(url, { method: "HEAD" });
779+
return response.ok;
761780
} catch {
762-
// Ignore fetch errors
781+
return false;
763782
}
764783
}
765784

src/w3c/templates/cgbg-headers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ export default (conf, options) => {
3737
>
3838
</dd>`
3939
: ""}
40-
${"latestVersion" in conf // latestVersion can be falsy
40+
${conf.latestVersion !== null
4141
? html`<dt>${l10n.latest_published_version}</dt>
4242
<dd>
43-
${conf.latestVersion
43+
${conf.latestVersion !== ""
4444
? html`<a href="${conf.latestVersion}"
4545
>${conf.latestVersion}</a
4646
>`

tests/spec/w3c/headers-spec.js

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ import {
1616
makeDefaultBody,
1717
makeRSDoc,
1818
makeStandardOps,
19+
warningFilters,
1920
} from "../SpecHelper.js";
2021

2122
const headerErrors = errorFilters.filter("w3c/headers");
23+
const headerWarnings = warningFilters.filter("w3c/headers");
2224
const defaultErrors = errorFilters.filter("w3c/defaults");
2325

2426
const findContent = string => {
@@ -62,26 +64,27 @@ describe("W3C — Headers", () => {
6264
expect(exportedDoc.querySelector(".head details[open]")).toBeTruthy();
6365
});
6466

65-
it("links to the 'kinds of documents' only for W3C documents", async () => {
66-
const statuses = ["FPWD", "WD", "CR", "CRD", "PR", "REC", "NOTE"];
67-
for (const specStatus of statuses) {
67+
for (const specStatus of recTrackStatus) {
68+
it(`links to the 'kinds of documents' only for W3C documents with status ${specStatus}`, async () => {
6869
const doc = await makeRSDoc(
6970
makeStandardOps({ specStatus, group: "webapps" })
7071
);
7172
const w3cLink = doc.querySelector(
7273
`.head a[href='https://www.w3.org/standards/types#${specStatus}']`
7374
);
74-
expect(w3cLink).withContext(`specStatus: ${specStatus}`).toBeTruthy();
75-
}
75+
expect(w3cLink).toBeTruthy();
76+
});
77+
}
7678

77-
for (const specStatus of ["unofficial", "base"]) {
79+
for (const specStatus of noTrackStatus) {
80+
it(`doesn't link to the 'kinds of documents' for non-rec track ${specStatus}`, async () => {
7881
const doc = await makeRSDoc(makeStandardOps({ specStatus }));
7982
const w3cLink = doc.querySelector(
8083
".head a[href='https://www.w3.org/standards/types#UD']"
8184
);
82-
expect(w3cLink).withContext(`specStatus: ${specStatus}`).toBeNull();
83-
}
84-
});
85+
expect(w3cLink).toBeNull();
86+
});
87+
}
8588

8689
describe("prevRecShortname & prevRecURI", () => {
8790
it("takes prevRecShortname and prevRecURI into account", async () => {
@@ -1437,6 +1440,21 @@ describe("W3C — Headers", () => {
14371440
expect(latestVersionLink.textContent).toBe("https://www.w3.org/TR/foo/");
14381441
});
14391442

1443+
it("warns if latestVersion URL doesn't exist", async () => {
1444+
const ops = makeStandardOps({
1445+
shortName: "foo",
1446+
specStatus: "WD",
1447+
group: "webapps",
1448+
github: "w3c/respec",
1449+
});
1450+
const doc = await makeRSDoc(ops);
1451+
const warnings = headerWarnings(doc);
1452+
expect(warnings).toHaveSize(1);
1453+
expect(warnings[0].message).toContain(
1454+
`The "Latest published version:" header link points to a URL that does not exist`
1455+
);
1456+
});
1457+
14401458
it("allows skipping latest published version link in initial ED", async () => {
14411459
const ops = makeStandardOps({
14421460
specStatus: "ED",
@@ -1543,19 +1561,15 @@ describe("W3C — Headers", () => {
15431561
);
15441562
});
15451563

1546-
for (const specStatus of cgbgStatus.filter(s => s.endsWith("-DRAFT"))) {
1564+
for (const specStatus of cgStatus.filter(s => s.endsWith("-DRAFT"))) {
15471565
it(`doesn't set latestVersion URL for ${specStatus} status`, async () => {
15481566
const ops = makeStandardOps({
15491567
shortName: "some-report",
15501568
specStatus,
15511569
group: "wicg",
15521570
});
15531571
const doc = await makeRSDoc(ops);
1554-
const terms = [...doc.querySelectorAll(".head dt")];
1555-
const latestVersion = terms.find(
1556-
el => el.textContent.trim() === "Latest published version:"
1557-
);
1558-
expect(latestVersion).toHaveSize(0);
1572+
expect(contains(doc, "dt", "Latest published version:")).toHaveSize(0);
15591573
});
15601574
}
15611575
for (const specStatus of noTrackStatus) {
@@ -1989,7 +2003,7 @@ describe("W3C — Headers", () => {
19892003
{ specStatus: "BG-FINAL", group: "publishingbg" },
19902004
];
19912005
for (const { specStatus, group } of finalReportStatus) {
1992-
it("requires that the ${specStatus} latestVersion be a w3c URL", async () => {
2006+
it(`requires that the ${specStatus} latestVersion be a w3c URL`, async () => {
19932007
const ops = makeStandardOps({
19942008
specStatus,
19952009
group,
@@ -2550,6 +2564,7 @@ describe("W3C — Headers", () => {
25502564
const ops = makeStandardOps({
25512565
shortName: "payment-request",
25522566
specStatus: "ED",
2567+
group: "payments",
25532568
});
25542569
const doc = await makeRSDoc(ops);
25552570
const [history] = contains(doc, ".head dt", "History:");
@@ -2562,8 +2577,8 @@ describe("W3C — Headers", () => {
25622577
);
25632578
});
25642579

2565-
for (const specStatus of trStatus) {
2566-
it(`includes the history for "${specStatus}" rec-track status`, async () => {
2580+
for (const specStatus of recTrackStatus) {
2581+
it(`includes the history for rec-track "${specStatus}" docs`, async () => {
25672582
const shortName = `push-api`;
25682583
const ops = makeStandardOps({
25692584
shortName,
@@ -2575,9 +2590,9 @@ describe("W3C — Headers", () => {
25752590
expect(history).withContext(specStatus).toBeTruthy();
25762591
expect(history.nextElementSibling).withContext(specStatus).toBeTruthy();
25772592
const historyLink = history.nextElementSibling.querySelector("a");
2578-
expect(historyLink).toBeTruthy();
2593+
expect(historyLink).withContext(specStatus).toBeTruthy();
25792594
expect(historyLink.href).toBe(
2580-
`https://www.w3.org/standards/history/${shortName}/`
2595+
`https://www.w3.org/standards/history/${shortName}`
25812596
);
25822597
});
25832598
}

0 commit comments

Comments
 (0)