rules/eslint/strings-punctuation.eslint.rule.ts
rules/eslint/strings-punctuation.eslint.rule.ts is a file in GovLab Extension Host. 116 lines of code and 25 definitions.
import { LONG_DASH, METRIC_UNITS, PUNCTUATION_POLICY, SEMICOLON } from "../../shared/manifests/punctuation.manifest.ts";
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { literalString, locOf, nodesAt, recordAt, stringIn } from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { basenameOf } from "../../shared/resolvers/anchor.resolver.ts";
import { codeLiteralsOf } from "../../shared/selectors/literal.selector.ts";
import { concernSuffix } from "../../shared/manifests/taxonomy.manifest.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";
const STRINGS_SUFFIX = concernSuffix("strings");
const WORD_STOPS = new Set([" ", "\n", "\t", ",", ";", ":", "!", "?", "(", ")", '"', "'", "*", "`"]);
const isDigit = function isDigit(char: string): boolean {
return char >= "0" && char <= "9";
};
const wordsOf = function wordsOf(text: string): string[] {
const words: string[] = [];
let current = "";
for (const char of text) {
if (WORD_STOPS.has(char)) {
if (current.length > 0) {
words.push(current);
}
current = "";
} else {
current += char;
}
}
if (current.length > 0) {
words.push(current);
}
return words;
};
const trimTerminal = function trimTerminal(word: string): string {
let end = word.length;
while (end > 0 && word.charAt(end - 1) === ".") {
end -= 1;
}
return word.slice(0, end);
};
const isNumeric = function isNumeric(stem: string): boolean {
for (let index = 0; index < stem.length; index += 1) {
const char = stem.charAt(index);
if (!isDigit(char) && char !== ".") {
return false;
}
}
return stem.length > 0;
};
const isDigitMetric = function isDigitMetric(word: string): boolean {
const lower = trimTerminal(word.toLowerCase());
const unit = METRIC_UNITS.find((held) => lower.endsWith(held) && lower.length > held.length);
return unit === undefined ? false : isNumeric(lower.slice(0, -unit.length));
};
const digitMetricIn = function digitMetricIn(text: string): string | null {
const found = wordsOf(text).find(isDigitMetric);
return found === undefined ? null : trimTerminal(found);
};
const quasiTexts = function quasiTexts(node: AstNode): string {
return nodesAt(node, "quasis")
.map((part) => stringIn(recordAt(part, "value"), "cooked"))
.join(" ");
};
export default {
create(context: RuleContext): RuleListener {
if (!basenameOf(context.filename).endsWith(STRINGS_SUFFIX)) {
return {};
}
let code: ReadonlySet<AstNode> = new Set();
const report = function report(node: AstNode, text: string): void {
if (code.has(node)) {
return;
}
if (PUNCTUATION_POLICY.longDash && text.includes(LONG_DASH)) {
context.report({ loc: locOf(node), messageId: "longDash" });
}
if (PUNCTUATION_POLICY.semicolon && text.includes(SEMICOLON)) {
context.report({ loc: locOf(node), messageId: "semicolon" });
}
const metric = PUNCTUATION_POLICY.digitMetric ? digitMetricIn(text) : null;
if (metric !== null) {
context.report({ data: { metric }, loc: locOf(node), messageId: "digitMetric" });
}
};
return listener({
literal(view) {
const text = literalString(view);
if (text !== null) {
report(view, text);
}
},
program(view) {
code = codeLiteralsOf(view);
},
templateLiteral(view) {
report(view, quasiTexts(view));
},
});
},
meta: {
docs: {
checks: defineCheck({ detects: [], enforces: ["architecture:standardization"] }),
description:
"A user-visible string honors the punctuation policy the registry declares: no long dash where the policy refuses it, no semicolon where it refuses it, and no digit-form metric standing beside a unit or a percent sign, because a number in copy is a claim that needs a measurement behind it. Each half of the policy is a boolean in the registry, so the operator turns a half on by editing data and the rule never changes. A literal consumed as a code sample is code, not prose, and is not held to the policy.",
},
messages: {
digitMetric:
"The literal states the digit-form metric '{{metric}}'. A number in user-visible copy is a measured claim; state the shape in words, or carry the measurement into a generated surface where the number is derived.",
longDash:
"The literal carries a long dash and the punctuation policy refuses it. Split the sentence or use the separator the policy allows.",
semicolon:
"The literal carries a semicolon and the punctuation policy refuses it. Split the sentence in two.",
},
schema: [],
type: "problem",
},
} satisfies LocalRule;