@descent-vtt/spec-brief 0.4.1 → 0.5.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/CHANGELOG.md +31 -0
- package/README.md +1 -0
- package/dist/archive.js +31 -18
- package/dist/archive.js.map +1 -1
- package/dist/brief.js +8 -5
- package/dist/brief.js.map +1 -1
- package/dist/cli.js +21 -13
- package/dist/cli.js.map +1 -1
- package/dist/collisions.d.ts +9 -0
- package/dist/collisions.js +50 -40
- package/dist/collisions.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +17 -6
- package/dist/config.js.map +1 -1
- package/dist/corpus.js +21 -15
- package/dist/corpus.js.map +1 -1
- package/dist/engine.d.ts +2 -1
- package/dist/engine.js +24 -15
- package/dist/engine.js.map +1 -1
- package/dist/frontmatter.js +1 -1
- package/dist/frontmatter.js.map +1 -1
- package/dist/fs.js +9 -5
- package/dist/fs.js.map +1 -1
- package/dist/git.js +7 -4
- package/dist/git.js.map +1 -1
- package/dist/glob.js +2 -0
- package/dist/glob.js.map +1 -1
- package/dist/links.js +2 -1
- package/dist/links.js.map +1 -1
- package/dist/lint.js +3 -4
- package/dist/lint.js.map +1 -1
- package/dist/plugins.js +10 -6
- package/dist/plugins.js.map +1 -1
- package/dist/report.d.ts +8 -1
- package/dist/report.js +22 -20
- package/dist/report.js.map +1 -1
- package/dist/rules.js +8 -4
- package/dist/rules.js.map +1 -1
- package/dist/scaffold.js +2 -2
- package/dist/scaffold.js.map +1 -1
- package/dist/schedule.js +10 -20
- package/dist/schedule.js.map +1 -1
- package/dist/schema.js +9 -13
- package/dist/schema.js.map +1 -1
- package/dist/text.d.ts +4 -1
- package/dist/text.js +10 -6
- package/dist/text.js.map +1 -1
- package/package.json +1 -1
package/dist/links.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"links.js","sourceRoot":"","sources":["../src/links.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Relative links, and keeping them true when a file moves.\n *\n * Both repositories this tool was measured against archive a brief by moving\n * it one directory down and then adding `../` to its links by hand. A move\n * that breaks links is a defect the move introduced, so the move repairs them:\n * the file's own relative links, and the links other live briefs hold to it.\n */\n\nimport type { Scan } from './markdown.js';\n\n/** A destination that is a path relative to the file holding it. */\nexport function isRelativeTarget(target: string): boolean {\n if (target === '' || target.startsWith('#') || target.startsWith('/') || target.startsWith('\\\\')) return false;\n return !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(target);\n}\n\n/** The path part of a destination and whatever follows it: `?query` or `#fragment`. */\nexport function splitTarget(target: string): { readonly path: string; readonly suffix: string } {\n const cut = target.search(/[?#]/);\n return cut < 0 ? { path: target, suffix: '' } : { path: target.slice(0, cut), suffix: target.slice(cut) };\n}\n\n/** Normalises a repository-relative path and refuses one that leaves the repository. */\nexport function normalisePath(path: string): string {\n const out: string[] = [];\n for (const part of path.replace(/\\\\/g, '/').split('/')) {\n if (part === '' || part === '.') continue;\n if (part === '..') {\n if (out.length === 0) throw new Error(`\"${path}\" is outside the repository`);\n out.pop();\n continue;\n }\n out.push(part);\n }\n return out.join('/');\n}\n\n/** Whether a repository path lies inside a directory. */\nexport function isInside(path: string, directory: string): boolean {\n const dir = normalisePath(directory);\n return dir === '' || path === dir || path.startsWith(`${dir}/`);\n}\n\nexport function dirOf(path: string): string {\n const slash = path.lastIndexOf('/');\n return slash < 0 ? '' : path.slice(0, slash);\n}\n\nfunction parts(path: string): string[] {\n return path.split('/').filter((p) => p !== '' && p !== '.');\n}\n\n/** A relative path resolved against a directory, or `null` when it climbs out of the repository. */\nexport function resolveFrom(directory: string, relative: string): string | null {\n const out = parts(directory);\n for (const part of parts(relative)) {\n if (part === '..') {\n if (out.length === 0) return null;\n out.pop();\n } else {\n out.push(part);\n }\n }\n return out.join('/');\n}\n\n/** The relative path from a directory to a repository path. */\nexport function relativePath(fromDirectory: string, to: string): string {\n const from = parts(fromDirectory);\n const target = parts(to);\n let common = 0;\n while (common < from.length && from[common] === target[common]) common += 1;\n const up = from.slice(common).map(() => '..');\n const rest = target.slice(common);\n const joined = [...up, ...rest].join('/');\n return joined === '' ? '.' : joined;\n}\n\nfunction decode(path: string): string {\n try {\n return decodeURIComponent(path);\n } catch {\n return path;\n }\n}\n\n/** Writes a path back in the encoding its original destination used. */\nfunction encodeLike(original: string, path: string): string {\n if (!original.includes('%')) return path;\n // encodeURIComponent leaves \".\" and \"..\" as they are.\n return path.split('/').map(encodeURIComponent).join('/');\n}\n\nexport interface LinkRewrite {\n readonly lines: string[];\n readonly count: number;\n}\n\n/**\n * Rewrites relative destinations for a file read from `directory` that will\n * be read from `newDirectory`. `retarget` maps the repository path a\n * destination resolves to now onto the one it should resolve to afterwards,\n * which differs only for a file that is itself moving.\n */\nexport function rewriteLinks(\n scanned: Scan,\n directory: string,\n newDirectory: string,\n retarget: (resolved: string) => string,\n): LinkRewrite {\n const lines = [...scanned.lines];\n let count = 0;\n const byLine = new Map<number, { start: number; end: number; replacement: string }[]>();\n for (const link of scanned.links) {\n if (!isRelativeTarget(link.target)) continue;\n const { path, suffix } = splitTarget(link.target);\n if (path === '') continue;\n const resolved = resolveFrom(directory, decode(path));\n if (resolved === null) continue;\n const destination = retarget(resolved);\n // A destination that still resolves where it should keeps its spelling.\n if (resolveFrom(newDirectory, decode(path)) === destination) continue;\n const relative = relativePath(newDirectory, destination);\n const trailing = path.endsWith('/') && relative !== '.' ? '/' : '';\n const replacement = `${encodeLike(path, relative)}${trailing}${suffix}`;\n byLine.set(link.line, [...(byLine.get(link.line) ?? []), { start: link.start, end: link.end, replacement }]);\n count += 1;\n }\n for (const [line, edits] of byLine) {\n let text = lines[line] as string;\n for (const edit of [...edits].sort((a, b) => b.start - a.start)) {\n text = text.slice(0, edit.start) + edit.replacement + text.slice(edit.end);\n }\n lines[line] = text;\n }\n return { lines, count };\n}\n\n/** Lines of `scanned` holding a relative link that resolves, from `directory`, to `target`. */\nexport function linesLinkingTo(scanned: Scan, directory: string, target: string): number[] {\n const found = new Set<number>();\n for (const link of scanned.links) {\n if (!isRelativeTarget(link.target)) continue;\n const { path } = splitTarget(link.target);\n if (path !== '' && resolveFrom(directory, decode(path)) === target) found.add(link.line);\n }\n // Links come in the order of their lines, so the set is already ascending.\n return [...found];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../src/links.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Relative links, and keeping them true when a file moves.\n *\n * Both repositories this tool was measured against archive a brief by moving\n * it one directory down and then adding `../` to its links by hand. A move\n * that breaks links is a defect the move introduced, so the move repairs them:\n * the file's own relative links, and the links other live briefs hold to it.\n */\n\nimport type { Scan } from './markdown.js';\n\n/** A destination that is a path relative to the file holding it. */\nexport function isRelativeTarget(target: string): boolean {\n if (target === '' || target.startsWith('#') || target.startsWith('/') || target.startsWith('\\\\')) return false;\n return !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(target);\n}\n\n/** The path part of a destination and whatever follows it: `?query` or `#fragment`. */\nexport function splitTarget(target: string): { readonly path: string; readonly suffix: string } {\n const cut = target.search(/[?#]/);\n return cut < 0 ? { path: target, suffix: '' } : { path: target.slice(0, cut), suffix: target.slice(cut) };\n}\n\n/** Normalises a repository-relative path and refuses one that leaves the repository. */\nexport function normalisePath(path: string): string {\n const out: string[] = [];\n for (const part of path.replace(/\\\\/g, '/').split('/')) {\n if (part === '' || part === '.') continue;\n if (part === '..') {\n if (out.length === 0) throw new Error(`\"${path}\" is outside the repository`);\n out.pop();\n continue;\n }\n out.push(part);\n }\n return out.join('/');\n}\n\n/** Whether a repository path lies inside a directory. */\nexport function isInside(path: string, directory: string): boolean {\n const dir = normalisePath(directory);\n return dir === '' || path === dir || path.startsWith(`${dir}/`);\n}\n\nexport function dirOf(path: string): string {\n const slash = path.lastIndexOf('/');\n return slash === -1 ? '' : path.slice(0, slash);\n}\n\nfunction parts(path: string): string[] {\n return path.split('/').filter((p) => p !== '' && p !== '.');\n}\n\n/** A relative path resolved against a directory, or `null` when it climbs out of the repository. */\nexport function resolveFrom(directory: string, relative: string): string | null {\n const out = parts(directory);\n for (const part of parts(relative)) {\n if (part === '..') {\n if (out.length === 0) return null;\n out.pop();\n } else {\n out.push(part);\n }\n }\n return out.join('/');\n}\n\n/** The relative path from a directory to a repository path. */\nexport function relativePath(fromDirectory: string, to: string): string {\n const from = parts(fromDirectory);\n const target = parts(to);\n let common = 0;\n // One step past the end compares nothing with what is left of the target, and the slices below come out the same.\n while (common < from.length && from[common] === target[common]) common += 1;\n const up = from.slice(common).map(() => '..');\n const rest = target.slice(common);\n const joined = [...up, ...rest].join('/');\n return joined === '' ? '.' : joined;\n}\n\nfunction decode(path: string): string {\n try {\n return decodeURIComponent(path);\n } catch {\n return path;\n }\n}\n\n/** Writes a path back in the encoding its original destination used. */\nfunction encodeLike(original: string, path: string): string {\n if (!original.includes('%')) return path;\n // encodeURIComponent leaves \".\" and \"..\" as they are.\n return path.split('/').map(encodeURIComponent).join('/');\n}\n\nexport interface LinkRewrite {\n readonly lines: string[];\n readonly count: number;\n}\n\n/**\n * Rewrites relative destinations for a file read from `directory` that will\n * be read from `newDirectory`. `retarget` maps the repository path a\n * destination resolves to now onto the one it should resolve to afterwards,\n * which differs only for a file that is itself moving.\n */\nexport function rewriteLinks(\n scanned: Scan,\n directory: string,\n newDirectory: string,\n retarget: (resolved: string) => string,\n): LinkRewrite {\n const lines = [...scanned.lines];\n let count = 0;\n const byLine = new Map<number, { start: number; end: number; replacement: string }[]>();\n for (const link of scanned.links) {\n if (!isRelativeTarget(link.target)) continue;\n const { path, suffix } = splitTarget(link.target);\n if (path === '') continue;\n const resolved = resolveFrom(directory, decode(path));\n if (resolved === null) continue;\n const destination = retarget(resolved);\n // A destination that still resolves where it should keeps its spelling.\n if (resolveFrom(newDirectory, decode(path)) === destination) continue;\n const relative = relativePath(newDirectory, destination);\n const trailing = path.endsWith('/') && relative !== '.' ? '/' : '';\n const replacement = `${encodeLike(path, relative)}${trailing}${suffix}`;\n byLine.set(link.line, [...(byLine.get(link.line) ?? []), { start: link.start, end: link.end, replacement }]);\n count += 1;\n }\n for (const [line, edits] of byLine) {\n let text = lines[line] as string;\n for (const edit of [...edits].sort((a, b) => b.start - a.start)) {\n text = text.slice(0, edit.start) + edit.replacement + text.slice(edit.end);\n }\n lines[line] = text;\n }\n return { lines, count };\n}\n\n/** Lines of `scanned` holding a relative link that resolves, from `directory`, to `target`. */\nexport function linesLinkingTo(scanned: Scan, directory: string, target: string): number[] {\n const found = new Set<number>();\n for (const link of scanned.links) {\n if (!isRelativeTarget(link.target)) continue;\n const { path } = splitTarget(link.target);\n if (path !== '' && resolveFrom(directory, decode(path)) === target) found.add(link.line);\n }\n // Links come in the order of their lines, so the set is already ascending.\n return [...found];\n}\n"]}
|
package/dist/lint.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ConfigError } from './config.js';
|
|
|
6
6
|
import { analyse, ARCHIVE_RULES, COLLISION_RULES, RULES } from './rules.js';
|
|
7
7
|
const RANK = { note: 0, warning: 1, error: 2 };
|
|
8
8
|
function lower(a, b) {
|
|
9
|
+
// Equal ranks are one severity, so either is the answer.
|
|
9
10
|
return RANK[a] <= RANK[b] ? a : b;
|
|
10
11
|
}
|
|
11
12
|
/** Every rule id a run knows: built-in, collision, archive and plugin. */
|
|
@@ -62,7 +63,7 @@ export async function lint(corpus, options = {}) {
|
|
|
62
63
|
const configured = severity;
|
|
63
64
|
findings.push({
|
|
64
65
|
rule: id,
|
|
65
|
-
severity: result.severity
|
|
66
|
+
severity: lower(result.severity ?? configured, configured),
|
|
66
67
|
message: result.message,
|
|
67
68
|
file: brief.file,
|
|
68
69
|
line: Math.max(1, Math.trunc(result.line)),
|
|
@@ -77,9 +78,7 @@ export async function lint(corpus, options = {}) {
|
|
|
77
78
|
}
|
|
78
79
|
/** Strings by code unit, which is the same order on every host and in every locale. */
|
|
79
80
|
function order(a, b) {
|
|
80
|
-
|
|
81
|
-
return 0;
|
|
82
|
-
return a < b ? -1 : 1;
|
|
81
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
83
82
|
}
|
|
84
83
|
export function sortFindings(findings) {
|
|
85
84
|
return [...findings].sort((a, b) => order(a.file, b.file) || a.line - b.line || order(a.rule, b.rule) || order(a.message, b.message));
|
package/dist/lint.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint.js","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Running rules: severities from the configuration, plugins beside the\n * built-in rules, findings in a stable order.\n */\n\nimport type { Waiver } from './archive.js';\nimport type { Brief } from './brief.js';\nimport { ConfigError } from './config.js';\nimport type { Corpus } from './corpus.js';\nimport { analyse, ARCHIVE_RULES, COLLISION_RULES, type Rule, type RuleInfo, RULES } from './rules.js';\nimport type { Finding, Severity, SeveritySetting } from './types.js';\n\n/** What a plugin's `waive` hook is asked about: one archival, refused. */\nexport interface WaiveContext {\n /** The absolute root: the directory holding the configuration. */\n readonly root: string;\n readonly brief: { readonly id: string | null; readonly file: string; readonly text: string };\n /**\n * Every finding that refuses the archival, as a frozen copy: writing to it\n * throws, and the waivers are matched against the plan's own. A\n * `protected-file` finding names its `path`; an `out-of-scope` one lists its\n * `paths`. Only those two can be waived.\n */\n readonly findings: readonly Finding[];\n /** The base the round is measured from, as it was given; `null` when none was. */\n readonly base: string | null;\n /** The commit the round landed as; `null` when none was read. */\n readonly commit: string | null;\n}\n\nexport interface Plugin {\n /** Prefixes the plugin's rule ids: `<name>/<rule>`. */\n readonly name: string;\n readonly rules: readonly Rule[];\n /** The plugin's entry in the configuration, handed to each rule. */\n readonly options?: unknown;\n /**\n * Lifts refusals of an archival that the plugin's own check allows - a\n * protected file a verified ruling covers - by rule and path. Asked only\n * when the plan has a refusal it could lift. Returning nothing waives\n * nothing.\n */\n readonly waive?: ((context: WaiveContext) => readonly Waiver[] | void | Promise<readonly Waiver[] | void>) | undefined;\n}\n\nexport interface LintOptions {\n readonly plugins?: readonly Plugin[];\n /** Tracked files, for the rules that read the tree. */\n readonly repoFiles?: readonly string[] | null;\n /** Limit the findings to these briefs. The whole corpus is still read. */\n readonly only?: readonly Brief[];\n}\n\nconst RANK: Readonly<Record<Severity, number>> = { note: 0, warning: 1, error: 2 };\n\nfunction lower(a: Severity, b: Severity): Severity {\n return RANK[a] <= RANK[b] ? a : b;\n}\n\ninterface ActiveRule {\n readonly id: string;\n readonly rule: Rule;\n readonly severity: SeveritySetting;\n readonly options: unknown;\n}\n\n/** Every rule id a run knows: built-in, collision, archive and plugin. */\nexport function ruleIds(plugins: readonly Plugin[] = []): string[] {\n return [\n ...RULES.map((r) => r.id),\n ...COLLISION_RULES.map((r) => r.id),\n ...ARCHIVE_RULES.map((r) => r.id),\n ...plugins.flatMap((p) => p.rules.map((r) => `${p.name}/${r.id}`)),\n ];\n}\n\n/** The severity configuration assigns a rule, or its own. */\nexport function severityOf(corpus: Corpus, id: string, fallback: SeveritySetting): SeveritySetting {\n return corpus.config.rules[id] ?? fallback;\n}\n\n/** The severity of a built-in rule as configured, or `null` when it is off. */\nexport function configuredSeverity(corpus: Corpus, id: string): Severity | null {\n const rule = [...RULES, ...COLLISION_RULES, ...ARCHIVE_RULES].find((r) => r.id === id) as RuleInfo;\n const setting = severityOf(corpus, id, rule.severity);\n return setting === 'off' ? null : setting;\n}\n\n/** Refuses a configuration that names a rule nobody defines: a typo there silences nothing. */\nexport function checkRuleIds(corpus: Corpus, plugins: readonly Plugin[] = []): void {\n const known = new Set(ruleIds(plugins));\n const unknown = Object.keys(corpus.config.rules).filter((id) => !known.has(id));\n if (unknown.length > 0) {\n throw new ConfigError('configuration', unknown.map((id) => `\"rules.${id}\" names no rule`));\n }\n}\n\nexport async function lint(corpus: Corpus, options: LintOptions = {}): Promise<Finding[]> {\n const plugins = options.plugins ?? [];\n checkRuleIds(corpus, plugins);\n const active: ActiveRule[] = [\n ...RULES.map((rule) => ({ id: rule.id, rule, severity: severityOf(corpus, rule.id, rule.severity), options: undefined })),\n ...plugins.flatMap((plugin) =>\n plugin.rules.map((rule) => {\n const id = `${plugin.name}/${rule.id}`;\n return { id, rule, severity: severityOf(corpus, id, rule.severity), options: plugin.options };\n }),\n ),\n ].filter((r) => r.severity !== 'off');\n\n const shared = analyse(corpus);\n const targets = options.only ?? corpus.briefs;\n const findings: Finding[] = [];\n for (const brief of targets) {\n for (const { id, rule, severity, options: ruleOptions } of active) {\n const results = await rule.check({\n brief,\n corpus,\n config: corpus.config,\n repoFiles: options.repoFiles ?? null,\n shared,\n options: ruleOptions,\n });\n for (const result of results) {\n const configured = severity as Severity;\n findings.push({\n rule: id,\n severity: result.severity === undefined ? configured : lower(result.severity, configured),\n message: result.message,\n file: brief.file,\n line: Math.max(1, Math.trunc(result.line)),\n brief: brief.id ?? undefined,\n hint: result.hint,\n subject: result.subject,\n });\n }\n }\n }\n return sortFindings(findings);\n}\n\n/** Strings by code unit, which is the same order on every host and in every locale. */\nfunction order(a: string, b: string): number {\n if (a === b) return 0;\n return a < b ? -1 : 1;\n}\n\nexport function sortFindings(findings: readonly Finding[]): Finding[] {\n return [...findings].sort(\n (a, b) => order(a.file, b.file) || a.line - b.line || order(a.rule, b.rule) || order(a.message, b.message),\n );\n}\n\nexport interface Summary {\n readonly errors: number;\n readonly warnings: number;\n readonly notes: number;\n}\n\nexport function summarise(findings: readonly Finding[]): Summary {\n return {\n errors: findings.filter((f) => f.severity === 'error').length,\n warnings: findings.filter((f) => f.severity === 'warning').length,\n notes: findings.filter((f) => f.severity === 'note').length,\n };\n}\n\n/** Whether findings fail a run: any error, or any warning under `--strict`. */\nexport function failing(findings: readonly Finding[], strict: boolean): boolean {\n return findings.some((f) => f.severity === 'error' || (strict && f.severity === 'warning'));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"lint.js","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Running rules: severities from the configuration, plugins beside the\n * built-in rules, findings in a stable order.\n */\n\nimport type { Waiver } from './archive.js';\nimport type { Brief } from './brief.js';\nimport { ConfigError } from './config.js';\nimport type { Corpus } from './corpus.js';\nimport { analyse, ARCHIVE_RULES, COLLISION_RULES, type Rule, type RuleInfo, RULES } from './rules.js';\nimport type { Finding, Severity, SeveritySetting } from './types.js';\n\n/** What a plugin's `waive` hook is asked about: one archival, refused. */\nexport interface WaiveContext {\n /** The absolute root: the directory holding the configuration. */\n readonly root: string;\n readonly brief: { readonly id: string | null; readonly file: string; readonly text: string };\n /**\n * Every finding that refuses the archival, as a frozen copy: writing to it\n * throws, and the waivers are matched against the plan's own. A\n * `protected-file` finding names its `path`; an `out-of-scope` one lists its\n * `paths`. Only those two can be waived.\n */\n readonly findings: readonly Finding[];\n /** The base the round is measured from, as it was given; `null` when none was. */\n readonly base: string | null;\n /** The commit the round landed as; `null` when none was read. */\n readonly commit: string | null;\n}\n\nexport interface Plugin {\n /** Prefixes the plugin's rule ids: `<name>/<rule>`. */\n readonly name: string;\n readonly rules: readonly Rule[];\n /** The plugin's entry in the configuration, handed to each rule. */\n readonly options?: unknown;\n /**\n * Lifts refusals of an archival that the plugin's own check allows - a\n * protected file a verified ruling covers - by rule and path. Asked only\n * when the plan has a refusal it could lift. Returning nothing waives\n * nothing.\n */\n readonly waive?: ((context: WaiveContext) => readonly Waiver[] | void | Promise<readonly Waiver[] | void>) | undefined;\n}\n\nexport interface LintOptions {\n readonly plugins?: readonly Plugin[];\n /** Tracked files, for the rules that read the tree. */\n readonly repoFiles?: readonly string[] | null;\n /** Limit the findings to these briefs. The whole corpus is still read. */\n readonly only?: readonly Brief[];\n}\n\nconst RANK: Readonly<Record<Severity, number>> = { note: 0, warning: 1, error: 2 };\n\nfunction lower(a: Severity, b: Severity): Severity {\n // Equal ranks are one severity, so either is the answer.\n return RANK[a] <= RANK[b] ? a : b;\n}\n\ninterface ActiveRule {\n readonly id: string;\n readonly rule: Rule;\n readonly severity: SeveritySetting;\n readonly options: unknown;\n}\n\n/** Every rule id a run knows: built-in, collision, archive and plugin. */\nexport function ruleIds(plugins: readonly Plugin[] = []): string[] {\n return [\n ...RULES.map((r) => r.id),\n ...COLLISION_RULES.map((r) => r.id),\n ...ARCHIVE_RULES.map((r) => r.id),\n ...plugins.flatMap((p) => p.rules.map((r) => `${p.name}/${r.id}`)),\n ];\n}\n\n/** The severity configuration assigns a rule, or its own. */\nexport function severityOf(corpus: Corpus, id: string, fallback: SeveritySetting): SeveritySetting {\n return corpus.config.rules[id] ?? fallback;\n}\n\n/** The severity of a built-in rule as configured, or `null` when it is off. */\nexport function configuredSeverity(corpus: Corpus, id: string): Severity | null {\n const rule = [...RULES, ...COLLISION_RULES, ...ARCHIVE_RULES].find((r) => r.id === id) as RuleInfo;\n const setting = severityOf(corpus, id, rule.severity);\n return setting === 'off' ? null : setting;\n}\n\n/** Refuses a configuration that names a rule nobody defines: a typo there silences nothing. */\nexport function checkRuleIds(corpus: Corpus, plugins: readonly Plugin[] = []): void {\n const known = new Set(ruleIds(plugins));\n const unknown = Object.keys(corpus.config.rules).filter((id) => !known.has(id));\n if (unknown.length > 0) {\n throw new ConfigError('configuration', unknown.map((id) => `\"rules.${id}\" names no rule`));\n }\n}\n\nexport async function lint(corpus: Corpus, options: LintOptions = {}): Promise<Finding[]> {\n const plugins = options.plugins ?? [];\n checkRuleIds(corpus, plugins);\n const active: ActiveRule[] = [\n ...RULES.map((rule) => ({ id: rule.id, rule, severity: severityOf(corpus, rule.id, rule.severity), options: undefined })),\n ...plugins.flatMap((plugin) =>\n plugin.rules.map((rule) => {\n const id = `${plugin.name}/${rule.id}`;\n return { id, rule, severity: severityOf(corpus, id, rule.severity), options: plugin.options };\n }),\n ),\n ].filter((r) => r.severity !== 'off');\n\n const shared = analyse(corpus);\n const targets = options.only ?? corpus.briefs;\n const findings: Finding[] = [];\n for (const brief of targets) {\n for (const { id, rule, severity, options: ruleOptions } of active) {\n const results = await rule.check({\n brief,\n corpus,\n config: corpus.config,\n repoFiles: options.repoFiles ?? null,\n shared,\n options: ruleOptions,\n });\n for (const result of results) {\n const configured = severity as Severity;\n findings.push({\n rule: id,\n severity: lower(result.severity ?? configured, configured),\n message: result.message,\n file: brief.file,\n line: Math.max(1, Math.trunc(result.line)),\n brief: brief.id ?? undefined,\n hint: result.hint,\n subject: result.subject,\n });\n }\n }\n }\n return sortFindings(findings);\n}\n\n/** Strings by code unit, which is the same order on every host and in every locale. */\nfunction order(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nexport function sortFindings(findings: readonly Finding[]): Finding[] {\n return [...findings].sort(\n (a, b) => order(a.file, b.file) || a.line - b.line || order(a.rule, b.rule) || order(a.message, b.message),\n );\n}\n\nexport interface Summary {\n readonly errors: number;\n readonly warnings: number;\n readonly notes: number;\n}\n\nexport function summarise(findings: readonly Finding[]): Summary {\n return {\n errors: findings.filter((f) => f.severity === 'error').length,\n warnings: findings.filter((f) => f.severity === 'warning').length,\n notes: findings.filter((f) => f.severity === 'note').length,\n };\n}\n\n/** Whether findings fail a run: any error, or any warning under `--strict`. */\nexport function failing(findings: readonly Finding[], strict: boolean): boolean {\n return findings.some((f) => f.severity === 'error' || (strict && f.severity === 'warning'));\n}\n"]}
|
package/dist/plugins.js
CHANGED
|
@@ -60,7 +60,8 @@ function exportTarget(value, match) {
|
|
|
60
60
|
* longest prefix, and of those the longest key.
|
|
61
61
|
*/
|
|
62
62
|
export function exportsTarget(exports, subpath) {
|
|
63
|
-
|
|
63
|
+
// Only a map of subpaths has a key that starts with a dot: a path's and a list's keys are their indices.
|
|
64
|
+
const keys = Object.keys(Object(exports));
|
|
64
65
|
const dotted = keys.filter((key) => key.startsWith('.'));
|
|
65
66
|
if (dotted.length === 0)
|
|
66
67
|
return subpath === '.' ? (exportTarget(exports, null) ?? null) : null;
|
|
@@ -73,11 +74,12 @@ export function exportsTarget(exports, subpath) {
|
|
|
73
74
|
let best;
|
|
74
75
|
for (const key of dotted) {
|
|
75
76
|
const star = key.indexOf('*');
|
|
76
|
-
if (star
|
|
77
|
+
if (star === -1 || key.includes('*', star + 1))
|
|
77
78
|
continue;
|
|
78
79
|
const suffix = key.slice(star + 1);
|
|
79
80
|
if (subpath.length < key.length || !subpath.startsWith(key.slice(0, star)) || !subpath.endsWith(suffix))
|
|
80
81
|
continue;
|
|
82
|
+
// Two keys of one prefix and one length that both match are one key, so the lengths never tie.
|
|
81
83
|
if (best === undefined || star > best.prefix || (star === best.prefix && key.length > best.key.length)) {
|
|
82
84
|
best = { key, prefix: star, match: subpath.slice(star, subpath.length - suffix.length) };
|
|
83
85
|
}
|
|
@@ -106,10 +108,12 @@ async function manifest(path) {
|
|
|
106
108
|
*/
|
|
107
109
|
async function resolvePackage(specifier, root) {
|
|
108
110
|
const slash = specifier.indexOf('/', specifier.startsWith('@') ? specifier.indexOf('/') + 1 : 0);
|
|
109
|
-
const name = slash
|
|
110
|
-
const subpath = slash
|
|
111
|
+
const name = slash === -1 ? specifier : specifier.slice(0, slash);
|
|
112
|
+
const subpath = slash === -1 ? '.' : `.${specifier.slice(slash)}`;
|
|
111
113
|
let directory = root;
|
|
112
|
-
// Bounded by the depth of the path: each pass moves one directory up
|
|
114
|
+
// Bounded by the depth of the path: each pass moves one directory up, and
|
|
115
|
+
// the search stops at the top, so no path reaches the bound and one pass
|
|
116
|
+
// more or fewer of it is the same search.
|
|
113
117
|
for (let depth = 0; depth < 256; depth += 1) {
|
|
114
118
|
const packageDirectory = join(directory, 'node_modules', name);
|
|
115
119
|
const found = await manifest(join(packageDirectory, 'package.json'));
|
|
@@ -122,6 +126,7 @@ async function resolvePackage(specifier, root) {
|
|
|
122
126
|
return join(packageDirectory, target);
|
|
123
127
|
}
|
|
124
128
|
const parent = dirname(directory);
|
|
129
|
+
// Stopping at the top saves asking it again until the bound: the answer is the same.
|
|
125
130
|
if (parent === directory)
|
|
126
131
|
break;
|
|
127
132
|
directory = parent;
|
|
@@ -140,7 +145,6 @@ function isRule(value) {
|
|
|
140
145
|
return (typeof rule['id'] === 'string' &&
|
|
141
146
|
/^[a-z0-9][a-z0-9-]*$/.test(rule['id']) &&
|
|
142
147
|
typeof rule['description'] === 'string' &&
|
|
143
|
-
typeof rule['severity'] === 'string' &&
|
|
144
148
|
SEVERITIES.has(rule['severity']) &&
|
|
145
149
|
typeof rule['check'] === 'function');
|
|
146
150
|
}
|
package/dist/plugins.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;AAEhE,yEAAyE;AACzE,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;AAEhF;;;;;;;GAOG;AACH,SAAS,YAAY,CAAC,KAAc,EAAE,KAAoB;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,MAAM,IAAI,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACnE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC3C,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACpH,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACvC,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;QAC5C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,KAAK,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC;gBAAE,SAAS;YAChD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACvC,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,IAAI,CAAC;QACtC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,OAAe;IAC7D,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACpH,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACzD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/F,gEAAgE;IAChE,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,GAAG,GAAG,OAAkC,CAAC;IAC/C,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC;QAAE,OAAO,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC;IAC3G,IAAI,IAA2F,CAAC;IAChG,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC;YAAE,SAAS;QACtD,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QACnC,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,SAAS;QAClH,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACvG,IAAI,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3F,CAAC;IACH,CAAC;IACD,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;AACvF,CAAC;AAED,8DAA8D;AAC9D,KAAK,UAAU,QAAQ,CAAC,IAAY;IAClC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAA4B,CAAC;IAC7E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,IAAK,KAA+B,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAChI,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,cAAc,CAAC,SAAiB,EAAE,IAAY;IAC3D,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjG,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/D,IAAI,SAAS,GAAG,IAAI,CAAC;IACrB,sEAAsE;IACtE,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC5C,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC,CAAC;QACrE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,IAAI,KAAK,CAAC,SAAS,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,KAAK,IAAI;gBAAE,MAAM;YACvE,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;YACxD,IAAI,MAAM,KAAK,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,gBAAgB,OAAO,cAAc,CAAC,CAAC;YACnF,OAAO,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QAClC,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM;QAChC,SAAS,GAAG,MAAM,CAAC;IACrB,CAAC;IACD,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AACtE,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,SAAiB,EAAE,IAAY;IACzD,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5G,OAAO,aAAa,CAAC,MAAM,cAAc,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACnE,CAAC;AAED,SAAS,MAAM,CAAC,KAAc;IAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,IAAI,GAAG,KAAgC,CAAC;IAC9C,OAAO,CACL,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,QAAQ;QAC9B,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC,aAAa,CAAC,KAAK,QAAQ;QACvC,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,QAAQ;QACpC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,UAAU,CACpC,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,QAAQ,CAAC,KAAc,EAAE,MAAc,EAAE,OAAgB;IACvE,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC,sEAAsE,CAAC,CAAC,CAAC;IAC1G,CAAC;IACD,MAAM,SAAS,GAAG,KAAgC,CAAC;IACnD,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAC7H,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;SAC9D,CAAC;QACJ,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;YACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,iFAAiF,CAAC,CAAC;QAChI,CAAC,CAAC,CAAC;IACL,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,UAAU;QAAE,QAAQ,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IACpG,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACjE,MAAM,MAAM,GAAW,EAAE,IAAI,EAAE,IAAc,EAAE,KAAK,EAAE,KAAe,EAAE,OAAO,EAAE,CAAC;IACjF,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,KAAqC,EAAE,CAAC;AACpG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc,EAAE,MAAc;IACtD,MAAM,QAAQ,GAAG,CAAC,IAAa,EAAkB,EAAE;QACjD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAC5D,MAAM,CAAC,GAAG,IAA+B,CAAC;QAC1C,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC;IAC3G,CAAC,CAAC;IACF,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,WAAW,CAAC,WAAW,MAAM,GAAG,EAAE,CAAC,qEAAqE,CAAC,CAAC,CAAC;IACvH,CAAC;IACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAsC,EAAE,IAAY;IACpF,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,MAA+B,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,YAAY,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAA4B,CAAC;QACjG,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,WAAW,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,wBAAyB,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAChG,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,MAAO,QAAoC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACzH,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAChE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC,0BAA0B,QAAQ,CAAC,CAAC,CAAW,GAAG,CAAC,CAAC,CAAC;IAChH,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["/**\n * Loading rule plugins named by the configuration.\n *\n * spec-brief does not know how another tool signs a departure, records a\n * reproducer or measures a blast radius, and should not: the tool that\n * defines a format is the one that can check it. A plugin is a module whose\n * default export is a plugin object, or a function of the configured options\n * that returns one. Loading one runs its code, exactly as loading an ESLint\n * configuration does; the configuration belongs to the repository, and so\n * does that trust.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport type { Waiver } from './archive.js';\nimport type { PluginReference } from './config.js';\nimport { ConfigError } from './config.js';\nimport type { Plugin } from './lint.js';\nimport type { Rule } from './rules.js';\n\nconst SEVERITIES = new Set(['off', 'note', 'warning', 'error']);\n\n/** The conditions Node matches in \"exports\" when it imports a module. */\nconst IMPORT_CONDITIONS = new Set(['node', 'import', 'module-sync', 'default']);\n\n/**\n * A target of \"exports\": a path inside the package, `null` where the package\n * refuses the subpath, `undefined` where no condition matched and a fallback\n * may still. A target that is not a \"./\" path, or that climbs out of the\n * package or into another, is refused, as Node refuses it: its segments are\n * split on a backslash too, since `join` reads one as a separator on Windows,\n * where \"pkg/..\\..\\outside\" would otherwise climb out of node_modules.\n */\nfunction exportTarget(value: unknown, match: string | null): string | null | undefined {\n if (typeof value === 'string') {\n if (!value.startsWith('./')) return null;\n const path = match === null ? value : value.replaceAll('*', match);\n const parts = path.slice(2).split(/[\\\\/]/);\n return parts.some((part) => part === '..' || part === '.' || part.toLowerCase() === 'node_modules') ? null : path;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n const path = exportTarget(item, match);\n if (typeof path === 'string') return path;\n }\n return null;\n }\n if (typeof value === 'object' && value !== null) {\n for (const [condition, item] of Object.entries(value)) {\n if (!IMPORT_CONDITIONS.has(condition)) continue;\n const path = exportTarget(item, match);\n if (path !== undefined) return path;\n }\n return undefined;\n }\n return null;\n}\n\n/**\n * The file a package's \"exports\" gives a subpath (`.` or `./x`) under the\n * conditions of an import, or `null` when it gives none: Node's\n * PACKAGE_EXPORTS_RESOLVE. An exact key wins; then the \"*\" pattern with the\n * longest prefix, and of those the longest key.\n */\nexport function exportsTarget(exports: unknown, subpath: string): string | null {\n const keys = typeof exports === 'object' && exports !== null && !Array.isArray(exports) ? Object.keys(exports) : [];\n const dotted = keys.filter((key) => key.startsWith('.'));\n if (dotted.length === 0) return subpath === '.' ? (exportTarget(exports, null) ?? null) : null;\n // Subpaths beside conditions is a package Node refuses to read.\n if (dotted.length !== keys.length) return null;\n const map = exports as Record<string, unknown>;\n if (!subpath.includes('*') && Object.hasOwn(map, subpath)) return exportTarget(map[subpath], null) ?? null;\n let best: { readonly key: string; readonly prefix: number; readonly match: string } | undefined;\n for (const key of dotted) {\n const star = key.indexOf('*');\n if (star < 0 || key.includes('*', star + 1)) continue;\n const suffix = key.slice(star + 1);\n if (subpath.length < key.length || !subpath.startsWith(key.slice(0, star)) || !subpath.endsWith(suffix)) continue;\n if (best === undefined || star > best.prefix || (star === best.prefix && key.length > best.key.length)) {\n best = { key, prefix: star, match: subpath.slice(star, subpath.length - suffix.length) };\n }\n }\n return best === undefined ? null : (exportTarget(map[best.key], best.match) ?? null);\n}\n\n/** A package.json, `undefined` when there is none to read. */\nasync function manifest(path: string): Promise<Record<string, unknown> | undefined> {\n try {\n return JSON.parse(await readFile(path, 'utf8')) as Record<string, unknown>;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT' || (error as NodeJS.ErrnoException).code === 'ENOTDIR') return undefined;\n throw error;\n }\n}\n\n/**\n * The file a package specifier names, found from the root the way an import\n * there would find it. `require.resolve` reads \"exports\" under the require\n * conditions, so a package that exports only for \"import\" - an ESM-only\n * plugin - could not be found; Node's `import.meta.resolve` resolves only\n * from the module calling it, not from the root. So the nearest\n * node_modules/<name> is found here and its \"exports\" read under the import\n * conditions; a package with no \"exports\" resolves as it always has.\n */\nasync function resolvePackage(specifier: string, root: string): Promise<string> {\n const slash = specifier.indexOf('/', specifier.startsWith('@') ? specifier.indexOf('/') + 1 : 0);\n const name = slash < 0 ? specifier : specifier.slice(0, slash);\n const subpath = slash < 0 ? '.' : `.${specifier.slice(slash)}`;\n let directory = root;\n // Bounded by the depth of the path: each pass moves one directory up.\n for (let depth = 0; depth < 256; depth += 1) {\n const packageDirectory = join(directory, 'node_modules', name);\n const found = await manifest(join(packageDirectory, 'package.json'));\n if (found !== undefined) {\n if (found['exports'] === undefined || found['exports'] === null) break;\n const target = exportsTarget(found['exports'], subpath);\n if (target === null) throw new Error(`${name} exports no \"${subpath}\" for import`);\n return join(packageDirectory, target);\n }\n const parent = dirname(directory);\n if (parent === directory) break;\n directory = parent;\n }\n return createRequire(join(root, 'package.json')).resolve(specifier);\n}\n\nasync function specifierUrl(specifier: string, root: string): Promise<string> {\n if (specifier.startsWith('.') || isAbsolute(specifier)) return pathToFileURL(resolve(root, specifier)).href;\n return pathToFileURL(await resolvePackage(specifier, root)).href;\n}\n\nfunction isRule(value: unknown): value is Rule {\n if (typeof value !== 'object' || value === null) return false;\n const rule = value as Record<string, unknown>;\n return (\n typeof rule['id'] === 'string' &&\n /^[a-z0-9][a-z0-9-]*$/.test(rule['id']) &&\n typeof rule['description'] === 'string' &&\n typeof rule['severity'] === 'string' &&\n SEVERITIES.has(rule['severity']) &&\n typeof rule['check'] === 'function'\n );\n}\n\n/** Checks the shape a module exported, since nothing typed it on the way in. */\nexport function asPlugin(value: unknown, module: string, options: unknown): Plugin {\n const problems: string[] = [];\n if (typeof value !== 'object' || value === null) {\n throw new ConfigError(module, ['does not export a plugin: expected an object with \"name\" and \"rules\"']);\n }\n const candidate = value as Record<string, unknown>;\n const name = candidate['name'];\n const rules = candidate['rules'];\n const waive = candidate['waive'];\n if (typeof name !== 'string' || !/^[a-z0-9@][a-z0-9@/._-]*$/i.test(name)) problems.push('\"name\" must be a plain identifier');\n if (!Array.isArray(rules)) problems.push('\"rules\" must be a list');\n else {\n rules.forEach((rule, i) => {\n if (!isRule(rule)) problems.push(`rules[${i}] needs a lower-case \"id\", a \"description\", a \"severity\" and a \"check\" function`);\n });\n }\n if (waive !== undefined && typeof waive !== 'function') problems.push('\"waive\" must be a function');\n if (problems.length > 0) throw new ConfigError(module, problems);\n const plugin: Plugin = { name: name as string, rules: rules as Rule[], options };\n return waive === undefined ? plugin : { ...plugin, waive: waive as NonNullable<Plugin['waive']> };\n}\n\n/**\n * Checks what a `waive` hook returned: a list of `{ rule, path, reason }`,\n * each a string. A hook that returns nothing waives nothing: a function that\n * found no ruling and fell off its end has answered, not failed.\n */\nexport function asWaivers(value: unknown, plugin: string): Waiver[] {\n const isWaiver = (item: unknown): item is Waiver => {\n if (typeof item !== 'object' || item === null) return false;\n const w = item as Record<string, unknown>;\n return typeof w['rule'] === 'string' && typeof w['path'] === 'string' && typeof w['reason'] === 'string';\n };\n if (value === undefined) return [];\n if (!Array.isArray(value) || !value.every(isWaiver)) {\n throw new ConfigError(`plugin \"${plugin}\"`, ['\"waive\" must return a list of { rule, path, reason }, each a string']);\n }\n return value.map((w) => ({ rule: w.rule, path: w.path, reason: w.reason }));\n}\n\nexport async function loadPlugins(references: readonly PluginReference[], root: string): Promise<Plugin[]> {\n const plugins: Plugin[] = [];\n for (const reference of references) {\n let loaded: Record<string, unknown>;\n try {\n loaded = (await import(await specifierUrl(reference.module, root))) as Record<string, unknown>;\n } catch (error) {\n throw new ConfigError(reference.module, [`could not be loaded: ${(error as Error).message}`]);\n }\n const exported = loaded['default'] ?? loaded['plugin'];\n const value = typeof exported === 'function' ? await (exported as (o: unknown) => unknown)(reference.options) : exported;\n plugins.push(asPlugin(value, reference.module, reference.options));\n }\n const names = plugins.map((p) => p.name);\n const repeated = names.filter((n, i) => names.indexOf(n) !== i);\n if (repeated.length > 0) throw new ConfigError('plugins', [`two plugins are named \"${repeated[0] as string}\"`]);\n return plugins;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Loading rule plugins named by the configuration.\n *\n * spec-brief does not know how another tool signs a departure, records a\n * reproducer or measures a blast radius, and should not: the tool that\n * defines a format is the one that can check it. A plugin is a module whose\n * default export is a plugin object, or a function of the configured options\n * that returns one. Loading one runs its code, exactly as loading an ESLint\n * configuration does; the configuration belongs to the repository, and so\n * does that trust.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport type { Waiver } from './archive.js';\nimport type { PluginReference } from './config.js';\nimport { ConfigError } from './config.js';\nimport type { Plugin } from './lint.js';\nimport type { Rule } from './rules.js';\n\nconst SEVERITIES = new Set(['off', 'note', 'warning', 'error']);\n\n/** The conditions Node matches in \"exports\" when it imports a module. */\nconst IMPORT_CONDITIONS = new Set(['node', 'import', 'module-sync', 'default']);\n\n/**\n * A target of \"exports\": a path inside the package, `null` where the package\n * refuses the subpath, `undefined` where no condition matched and a fallback\n * may still. A target that is not a \"./\" path, or that climbs out of the\n * package or into another, is refused, as Node refuses it: its segments are\n * split on a backslash too, since `join` reads one as a separator on Windows,\n * where \"pkg/..\\..\\outside\" would otherwise climb out of node_modules.\n */\nfunction exportTarget(value: unknown, match: string | null): string | null | undefined {\n if (typeof value === 'string') {\n if (!value.startsWith('./')) return null;\n const path = match === null ? value : value.replaceAll('*', match);\n const parts = path.slice(2).split(/[\\\\/]/);\n return parts.some((part) => part === '..' || part === '.' || part.toLowerCase() === 'node_modules') ? null : path;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n const path = exportTarget(item, match);\n if (typeof path === 'string') return path;\n }\n return null;\n }\n if (typeof value === 'object' && value !== null) {\n for (const [condition, item] of Object.entries(value)) {\n if (!IMPORT_CONDITIONS.has(condition)) continue;\n const path = exportTarget(item, match);\n if (path !== undefined) return path;\n }\n return undefined;\n }\n return null;\n}\n\n/**\n * The file a package's \"exports\" gives a subpath (`.` or `./x`) under the\n * conditions of an import, or `null` when it gives none: Node's\n * PACKAGE_EXPORTS_RESOLVE. An exact key wins; then the \"*\" pattern with the\n * longest prefix, and of those the longest key.\n */\nexport function exportsTarget(exports: unknown, subpath: string): string | null {\n // Only a map of subpaths has a key that starts with a dot: a path's and a list's keys are their indices.\n const keys = Object.keys(Object(exports) as object);\n const dotted = keys.filter((key) => key.startsWith('.'));\n if (dotted.length === 0) return subpath === '.' ? (exportTarget(exports, null) ?? null) : null;\n // Subpaths beside conditions is a package Node refuses to read.\n if (dotted.length !== keys.length) return null;\n const map = exports as Record<string, unknown>;\n if (!subpath.includes('*') && Object.hasOwn(map, subpath)) return exportTarget(map[subpath], null) ?? null;\n let best: { readonly key: string; readonly prefix: number; readonly match: string } | undefined;\n for (const key of dotted) {\n const star = key.indexOf('*');\n if (star === -1 || key.includes('*', star + 1)) continue;\n const suffix = key.slice(star + 1);\n if (subpath.length < key.length || !subpath.startsWith(key.slice(0, star)) || !subpath.endsWith(suffix)) continue;\n // Two keys of one prefix and one length that both match are one key, so the lengths never tie.\n if (best === undefined || star > best.prefix || (star === best.prefix && key.length > best.key.length)) {\n best = { key, prefix: star, match: subpath.slice(star, subpath.length - suffix.length) };\n }\n }\n return best === undefined ? null : (exportTarget(map[best.key], best.match) ?? null);\n}\n\n/** A package.json, `undefined` when there is none to read. */\nasync function manifest(path: string): Promise<Record<string, unknown> | undefined> {\n try {\n return JSON.parse(await readFile(path, 'utf8')) as Record<string, unknown>;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT' || (error as NodeJS.ErrnoException).code === 'ENOTDIR') return undefined;\n throw error;\n }\n}\n\n/**\n * The file a package specifier names, found from the root the way an import\n * there would find it. `require.resolve` reads \"exports\" under the require\n * conditions, so a package that exports only for \"import\" - an ESM-only\n * plugin - could not be found; Node's `import.meta.resolve` resolves only\n * from the module calling it, not from the root. So the nearest\n * node_modules/<name> is found here and its \"exports\" read under the import\n * conditions; a package with no \"exports\" resolves as it always has.\n */\nasync function resolvePackage(specifier: string, root: string): Promise<string> {\n const slash = specifier.indexOf('/', specifier.startsWith('@') ? specifier.indexOf('/') + 1 : 0);\n const name = slash === -1 ? specifier : specifier.slice(0, slash);\n const subpath = slash === -1 ? '.' : `.${specifier.slice(slash)}`;\n let directory = root;\n // Bounded by the depth of the path: each pass moves one directory up, and\n // the search stops at the top, so no path reaches the bound and one pass\n // more or fewer of it is the same search.\n for (let depth = 0; depth < 256; depth += 1) {\n const packageDirectory = join(directory, 'node_modules', name);\n const found = await manifest(join(packageDirectory, 'package.json'));\n if (found !== undefined) {\n if (found['exports'] === undefined || found['exports'] === null) break;\n const target = exportsTarget(found['exports'], subpath);\n if (target === null) throw new Error(`${name} exports no \"${subpath}\" for import`);\n return join(packageDirectory, target);\n }\n const parent = dirname(directory);\n // Stopping at the top saves asking it again until the bound: the answer is the same.\n if (parent === directory) break;\n directory = parent;\n }\n return createRequire(join(root, 'package.json')).resolve(specifier);\n}\n\nasync function specifierUrl(specifier: string, root: string): Promise<string> {\n if (specifier.startsWith('.') || isAbsolute(specifier)) return pathToFileURL(resolve(root, specifier)).href;\n return pathToFileURL(await resolvePackage(specifier, root)).href;\n}\n\nfunction isRule(value: unknown): value is Rule {\n if (typeof value !== 'object' || value === null) return false;\n const rule = value as Record<string, unknown>;\n return (\n typeof rule['id'] === 'string' &&\n /^[a-z0-9][a-z0-9-]*$/.test(rule['id']) &&\n typeof rule['description'] === 'string' &&\n SEVERITIES.has(rule['severity'] as string) &&\n typeof rule['check'] === 'function'\n );\n}\n\n/** Checks the shape a module exported, since nothing typed it on the way in. */\nexport function asPlugin(value: unknown, module: string, options: unknown): Plugin {\n const problems: string[] = [];\n if (typeof value !== 'object' || value === null) {\n throw new ConfigError(module, ['does not export a plugin: expected an object with \"name\" and \"rules\"']);\n }\n const candidate = value as Record<string, unknown>;\n const name = candidate['name'];\n const rules = candidate['rules'];\n const waive = candidate['waive'];\n if (typeof name !== 'string' || !/^[a-z0-9@][a-z0-9@/._-]*$/i.test(name)) problems.push('\"name\" must be a plain identifier');\n if (!Array.isArray(rules)) problems.push('\"rules\" must be a list');\n else {\n rules.forEach((rule, i) => {\n if (!isRule(rule)) problems.push(`rules[${i}] needs a lower-case \"id\", a \"description\", a \"severity\" and a \"check\" function`);\n });\n }\n if (waive !== undefined && typeof waive !== 'function') problems.push('\"waive\" must be a function');\n if (problems.length > 0) throw new ConfigError(module, problems);\n const plugin: Plugin = { name: name as string, rules: rules as Rule[], options };\n return waive === undefined ? plugin : { ...plugin, waive: waive as NonNullable<Plugin['waive']> };\n}\n\n/**\n * Checks what a `waive` hook returned: a list of `{ rule, path, reason }`,\n * each a string. A hook that returns nothing waives nothing: a function that\n * found no ruling and fell off its end has answered, not failed.\n */\nexport function asWaivers(value: unknown, plugin: string): Waiver[] {\n const isWaiver = (item: unknown): item is Waiver => {\n if (typeof item !== 'object' || item === null) return false;\n const w = item as Record<string, unknown>;\n return typeof w['rule'] === 'string' && typeof w['path'] === 'string' && typeof w['reason'] === 'string';\n };\n if (value === undefined) return [];\n if (!Array.isArray(value) || !value.every(isWaiver)) {\n throw new ConfigError(`plugin \"${plugin}\"`, ['\"waive\" must return a list of { rule, path, reason }, each a string']);\n }\n return value.map((w) => ({ rule: w.rule, path: w.path, reason: w.reason }));\n}\n\nexport async function loadPlugins(references: readonly PluginReference[], root: string): Promise<Plugin[]> {\n const plugins: Plugin[] = [];\n for (const reference of references) {\n let loaded: Record<string, unknown>;\n try {\n loaded = (await import(await specifierUrl(reference.module, root))) as Record<string, unknown>;\n } catch (error) {\n throw new ConfigError(reference.module, [`could not be loaded: ${(error as Error).message}`]);\n }\n const exported = loaded['default'] ?? loaded['plugin'];\n const value = typeof exported === 'function' ? await (exported as (o: unknown) => unknown)(reference.options) : exported;\n plugins.push(asPlugin(value, reference.module, reference.options));\n }\n const names = plugins.map((p) => p.name);\n const repeated = names.filter((n, i) => names.indexOf(n) !== i);\n if (repeated.length > 0) throw new ConfigError('plugins', [`two plugins are named \"${repeated[0] as string}\"`]);\n return plugins;\n}\n"]}
|
package/dist/report.d.ts
CHANGED
|
@@ -28,7 +28,14 @@ export declare const JSON_SCHEMA_VERSION = 2;
|
|
|
28
28
|
export interface Style {
|
|
29
29
|
readonly color: boolean;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
/** The SGR code that turns each colour on, and the one that turns it off again. */
|
|
32
|
+
declare const CODES: {
|
|
33
|
+
readonly red: readonly [31, 39];
|
|
34
|
+
readonly yellow: readonly [33, 39];
|
|
35
|
+
readonly cyan: readonly [36, 39];
|
|
36
|
+
readonly dim: readonly [2, 22];
|
|
37
|
+
readonly bold: readonly [1, 22];
|
|
38
|
+
};
|
|
32
39
|
export declare function paint(style: Style, color: keyof typeof CODES, text: string): string;
|
|
33
40
|
export declare function summaryLine(findings: readonly Finding[]): string;
|
|
34
41
|
export declare function prettyFindings(findings: readonly Finding[], style: Style): string;
|
package/dist/report.js
CHANGED
|
@@ -26,17 +26,17 @@ export const FORMATS = ['pretty', 'json', 'sarif', 'github', 'gitlab'];
|
|
|
26
26
|
* `waiting`.
|
|
27
27
|
*/
|
|
28
28
|
export const JSON_SCHEMA_VERSION = 2;
|
|
29
|
+
/** The SGR code that turns each colour on, and the one that turns it off again. */
|
|
29
30
|
const CODES = {
|
|
30
31
|
red: [31, 39],
|
|
31
32
|
yellow: [33, 39],
|
|
32
33
|
cyan: [36, 39],
|
|
33
|
-
green: [32, 39],
|
|
34
34
|
dim: [2, 22],
|
|
35
35
|
bold: [1, 22],
|
|
36
36
|
};
|
|
37
37
|
export function paint(style, color, text) {
|
|
38
|
-
const
|
|
39
|
-
return style.color
|
|
38
|
+
const [on, off] = CODES[color];
|
|
39
|
+
return style.color ? `\u001b[${on}m${text}\u001b[${off}m` : text;
|
|
40
40
|
}
|
|
41
41
|
const SEVERITY_COLOR = { error: 'red', warning: 'yellow', note: 'cyan' };
|
|
42
42
|
function plural(count, word) {
|
|
@@ -243,7 +243,8 @@ export function titleWithoutId(title, id) {
|
|
|
243
243
|
const text = title.trimStart();
|
|
244
244
|
// An id of digits is compared by value, so the title's own leading digits
|
|
245
245
|
// are the candidate. A title with none has `''`, which no id's key equals;
|
|
246
|
-
// any other text in its place
|
|
246
|
+
// any other text in its place, digits found further in included, fails
|
|
247
|
+
// `startsWith` and answers the same.
|
|
247
248
|
const lead = /^\d+$/.test(id) ? (/^\d+/.exec(text)?.[0] ?? '') : id;
|
|
248
249
|
if (!text.startsWith(lead) || idKey(lead) !== idKey(id))
|
|
249
250
|
return title;
|
|
@@ -270,9 +271,8 @@ function padStart(text, width) {
|
|
|
270
271
|
function table(header, rows) {
|
|
271
272
|
const all = [header, ...rows];
|
|
272
273
|
const widths = header.map((_, c) => Math.max(...all.map((r) => displayWidth(r[c]))));
|
|
273
|
-
// Padding the last cell would change nothing: trimEnd takes it off again.
|
|
274
274
|
return all.map((r) => r
|
|
275
|
-
.map((cell, c) =>
|
|
275
|
+
.map((cell, c) => padEnd(cell, widths[c]))
|
|
276
276
|
.join(' ')
|
|
277
277
|
.trimEnd());
|
|
278
278
|
}
|
|
@@ -297,26 +297,28 @@ export function prettyMatrix(report, style) {
|
|
|
297
297
|
out.push(paint(style, 'bold', `${title} \u00b7 ${plural(wave.briefs.length, 'brief')}`));
|
|
298
298
|
const names = wave.briefs.map(label);
|
|
299
299
|
const width = Math.max(3, ...names.map(displayWidth));
|
|
300
|
-
|
|
300
|
+
// A pair is marked by where its briefs stand in the wave, not by their
|
|
301
|
+
// labels: two briefs that share an id are two rows, and may collide. No
|
|
302
|
+
// pair is of a brief with itself, so the diagonal needs no rule of its own.
|
|
303
|
+
const place = new Map(wave.briefs.map((b, i) => [b, i]));
|
|
304
|
+
const pairs = (list) => new Set(list.flatMap((p) => [`${place.get(p.a)},${place.get(p.b)}`, `${place.get(p.b)},${place.get(p.a)}`]));
|
|
301
305
|
const hits = pairs(wave.collisions);
|
|
302
306
|
const unknown = pairs(wave.undecided);
|
|
303
307
|
const near = pairs(wave.shared);
|
|
304
308
|
out.push(` ${' '.repeat(width)} ${names.map((n) => padStart(n, width)).join(' ')}`);
|
|
305
|
-
for (const row of names) {
|
|
306
|
-
const cells = names.map((col) => {
|
|
307
|
-
const key = `${row}
|
|
308
|
-
const mark =
|
|
309
|
-
? '
|
|
310
|
-
:
|
|
311
|
-
? paint(style, '
|
|
312
|
-
:
|
|
313
|
-
? paint(style, 'yellow', '
|
|
314
|
-
:
|
|
315
|
-
? paint(style, 'yellow', '~')
|
|
316
|
-
: '\u00b7';
|
|
309
|
+
for (const [row, name] of names.entries()) {
|
|
310
|
+
const cells = names.map((_, col) => {
|
|
311
|
+
const key = `${row},${col}`;
|
|
312
|
+
const mark = hits.has(key)
|
|
313
|
+
? paint(style, 'red', 'X')
|
|
314
|
+
: unknown.has(key)
|
|
315
|
+
? paint(style, 'yellow', '?')
|
|
316
|
+
: near.has(key)
|
|
317
|
+
? paint(style, 'yellow', '~')
|
|
318
|
+
: '\u00b7';
|
|
317
319
|
return `${' '.repeat(width - 1)}${mark}`;
|
|
318
320
|
});
|
|
319
|
-
out.push(` ${padEnd(
|
|
321
|
+
out.push(` ${padEnd(name, width)} ${cells.join(' ')}`);
|
|
320
322
|
}
|
|
321
323
|
// One mark per pair of briefs; each further pair of patterns on a line beneath it.
|
|
322
324
|
for (const c of wave.collisions) {
|