Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions profiles/aom.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const modules = [
import("../src/core/linter-rules/no-unused-vars.js"),
import("../src/core/linter-rules/privsec-section.js"),
import("../src/core/linter-rules/no-http-props.js"),
import("../src/core/linter-rules/prefer-xref.js"),
];

Promise.all(modules)
Expand Down
1 change: 1 addition & 0 deletions profiles/dini.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const modules = [
import("../src/core/linter-rules/no-unused-vars.js"),
import("../src/core/linter-rules/privsec-section.js"),
import("../src/core/linter-rules/no-http-props.js"),
import("../src/core/linter-rules/prefer-xref.js"),
];

Promise.all(modules)
Expand Down
1 change: 1 addition & 0 deletions profiles/geonovum.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const modules = [
import("../src/core/linter-rules/no-unused-vars.js"),
import("../src/core/linter-rules/privsec-section.js"),
import("../src/core/linter-rules/no-http-props.js"),
import("../src/core/linter-rules/prefer-xref.js"),
];

Promise.all(modules)
Expand Down
1 change: 1 addition & 0 deletions profiles/w3c.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const modules = [
import("../src/core/linter-rules/no-http-props.js"),
import("../src/core/linter-rules/a11y.js"),
import("../src/core/linter-rules/informative-dfn.js"),
import("../src/core/linter-rules/prefer-xref.js"),
];

Promise.all(modules)
Expand Down
127 changes: 127 additions & 0 deletions src/core/linter-rules/prefer-xref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// @ts-check
import { docLink, getIntlData, showError, showWarning } from "../utils.js";
import { profiles } from "../xref.js";

const ruleName = "prefer-xref";
export const name = "core/linter-rules/prefer-xref";

/** @satisfies {Record<string, { msg(specKey: string): string; readonly hint: string }>} */
const localizationStrings = {
en: {
msg(specKey) {
return `Spec \`${specKey}\` is available in xref. Consider using shorthand syntax (e.g. \`[= term =]\`) instead of \`data-cite="${specKey}#…"\`.`;
},
get hint() {
return docLink`Using ${"[xref]"} shorthand syntax is shorter, spec-version-agnostic, and lets ReSpec verify the term exists. To silence this warning for a specific element, add \`class="lint-ignore"\`. To disable this rule entirely, set \`lint: { "${ruleName}": false }\` in your \`respecConfig\`.`;
},
},
cs: {
msg(specKey) {
return `Specifikace \`${specKey}\` je dostupná v xref. Zvažte použití zkráceného zápisu (např. \`[= pojem =]\`) místo \`data-cite="${specKey}#…"\`.`;
},
get hint() {
return docLink`Zkrácený zápis ${"[xref]"} je kratší, nezávislý na verzi specifikace a umožňuje ReSpecu ověřit existenci pojmu. Pro potlačení tohoto varování u konkrétního prvku přidejte \`class="lint-ignore"\`. Pro úplné vypnutí pravidla nastavte \`lint: { "${ruleName}": false }\` ve vašem \`respecConfig\`.`;
},
},
};
const l10n = getIntlData(localizationStrings);

/**
* @param {Conf["xref"]} xref
* @returns {Set<string> | null}
*/
function getXrefSpecSet(xref) {
if (!xref || xref === true) return null;

/** @type {Set<string>} */
const specs = new Set();

if (typeof xref === "string") {
const profile = xref.toLowerCase();
if (profile in profiles) {
/** @type {Record<string, string[]>} */ (profiles)[profile].forEach(
(/** @type {string} */ s) => specs.add(s.toUpperCase())
);
}
return specs.size ? specs : null;
}

if (Array.isArray(xref)) {
xref.forEach(s => specs.add(s.toUpperCase()));
return specs.size ? specs : null;
}

if (typeof xref === "object") {
const { profile, specs: specList } =
/** @type {{ profile?: string; specs?: string[] }} */ (xref);
if (profile) {
const key = profile.toLowerCase();
if (key in profiles) {
/** @type {Record<string, string[]>} */ (profiles)[key].forEach(
(/** @type {string} */ s) => specs.add(s.toUpperCase())
);
}
}
if (specList) {
specList.forEach(s => specs.add(s.toUpperCase()));
}
return specs.size ? specs : null;
}

return null;
}

/**
* @param {string} rawCite
* @returns {string}
*/
function extractSpecKey(rawCite) {
return rawCite.replace(/^[?!]/, "").split(/[/#]/)[0].toUpperCase();
Comment thread
marcoscaceres marked this conversation as resolved.
}

/**
* @param {Conf} conf
*/
export function run(conf) {
// @ts-expect-error -- LintConfig can be false; ?. only short-circuits null/undefined in TS
if (!conf.lint?.[ruleName]) {
return;
}

if (!conf.xref) {
return;
}

const xrefSpecSet = getXrefSpecSet(conf.xref);
if (!xrefSpecSet) {
return;
}

/** @type {NodeListOf<HTMLElement>} */
const elems = document.querySelectorAll(
":is(a, dfn)[data-cite*='#']:not([data-cite^='#']):not(.lint-ignore)"
);

/** @type {Map<string, HTMLElement[]>} */
const offenders = new Map();

elems.forEach(elem => {
const rawCite = /** @type {string} */ (elem.dataset.cite);
const specKey = extractSpecKey(rawCite);
if (!specKey || !xrefSpecSet.has(specKey)) return;

if (!offenders.has(specKey)) {
offenders.set(specKey, []);
}
/** @type {HTMLElement[]} */ (offenders.get(specKey)).push(elem);
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
marcoscaceres marked this conversation as resolved.
});
Comment thread
marcoscaceres marked this conversation as resolved.

// @ts-expect-error -- ruleName is a string literal but LintConfig index signature doesn't cover it
const logger = conf.lint?.[ruleName] === "error" ? showError : showWarning;
offenders.forEach((elements, specKey) => {
logger(l10n.msg(specKey), name, {
hint: l10n.hint,
elements,
});
});
Comment thread
marcoscaceres marked this conversation as resolved.
}
2 changes: 1 addition & 1 deletion src/core/xref.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { sub } from "./pubsubhub.js";

export const name = "core/xref";

const profiles = {
export const profiles = {
"web-platform": ["HTML", "INFRA", "URL", "WEBIDL", "DOM", "FETCH"],
};

Expand Down
Loading