@x18n/builtins 0.0.13 → 0.0.15
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/angular-templates-DqIOGfzT.mjs +8 -0
- package/dist/checks.d.mts +1 -0
- package/dist/checks.mjs +26 -1
- package/dist/extractors.mjs +7 -3
- package/dist/finders.d.mts +4 -1
- package/dist/finders.mjs +1 -1
- package/dist/formats/ecmascript.d.mts +5 -0
- package/dist/formats/ecmascript.mjs +232 -0
- package/dist/formats/ftl.d.mts +5 -0
- package/dist/formats/ftl.mjs +234 -0
- package/dist/formats/ini.d.mts +5 -0
- package/dist/formats/ini.mjs +158 -0
- package/dist/formats/json.d.mts +6 -0
- package/dist/formats/json.mjs +10 -0
- package/dist/formats/json5.d.mts +5 -0
- package/dist/formats/json5.mjs +143 -0
- package/dist/formats/jsonc.d.mts +6 -0
- package/dist/formats/jsonc.mjs +10 -0
- package/dist/formats/php.d.mts +5 -0
- package/dist/formats/php.mjs +198 -0
- package/dist/formats/po.d.mts +5 -0
- package/dist/formats/po.mjs +177 -0
- package/dist/formats/properties.d.mts +5 -0
- package/dist/formats/properties.mjs +123 -0
- package/dist/formats/resx.d.mts +6 -0
- package/dist/formats/resx.mjs +202 -0
- package/dist/formats/yaml.d.mts +5 -0
- package/dist/formats/yaml.mjs +140 -0
- package/dist/formats.d.mts +10 -34
- package/dist/formats.mjs +61 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +30 -22
- package/dist/{vue-CF4pe-Yr.mjs → js-CV-ECNJG.mjs} +67 -222
- package/dist/json-CGNSGS97.mjs +109 -0
- package/dist/json-CyjI8bjp.d.mts +8 -0
- package/dist/jsx-templates-B1J0no3i.mjs +17 -0
- package/dist/literal-gIdB1VGy.mjs +125 -0
- package/dist/locale-root-C_rch3IQ.mjs +15 -0
- package/dist/manifests.d.mts +8 -0
- package/dist/manifests.mjs +66 -2
- package/dist/placeholders.d.mts +11 -0
- package/dist/placeholders.mjs +28 -1
- package/dist/plugin-Cb1b0axD.mjs +83 -0
- package/dist/plural-forms-B7_QX4nG.mjs +12 -0
- package/dist/presets/cargo-i18n.d.mts +6 -0
- package/dist/presets/cargo-i18n.mjs +28 -0
- package/dist/presets/chrome-extension.d.mts +6 -0
- package/dist/presets/chrome-extension.mjs +52 -0
- package/dist/presets/dotnet-resx.d.mts +6 -0
- package/dist/presets/dotnet-resx.mjs +64 -0
- package/dist/presets/easy-localization.d.mts +6 -0
- package/dist/presets/easy-localization.mjs +36 -0
- package/dist/presets/ember-intl.d.mts +5 -0
- package/dist/presets/ember-intl.mjs +31 -0
- package/dist/presets/fluent-react.d.mts +5 -0
- package/dist/presets/fluent-react.mjs +46 -0
- package/dist/presets/fluent-vue.d.mts +5 -0
- package/dist/presets/fluent-vue.mjs +51 -0
- package/dist/presets/flutter-translate.d.mts +6 -0
- package/dist/presets/flutter-translate.mjs +30 -0
- package/dist/presets/flutter.d.mts +6 -0
- package/dist/presets/flutter.mjs +21 -0
- package/dist/presets/general.d.mts +6 -0
- package/dist/presets/general.mjs +17 -0
- package/dist/presets/globalize.d.mts +5 -0
- package/dist/presets/globalize.mjs +25 -0
- package/dist/presets/gravity-ui-i18n.d.mts +6 -0
- package/dist/presets/gravity-ui-i18n.mjs +41 -0
- package/dist/presets/i18n-tag.d.mts +5 -0
- package/dist/presets/i18n-tag.mjs +21 -0
- package/dist/presets/i18next-shopify.d.mts +6 -0
- package/dist/presets/i18next-shopify.mjs +23 -0
- package/dist/presets/i18next.d.mts +5 -0
- package/dist/presets/i18next.mjs +10 -0
- package/dist/presets/i18nice.d.mts +6 -0
- package/dist/presets/i18nice.mjs +29 -0
- package/dist/presets/jekyll.d.mts +5 -0
- package/dist/presets/jekyll.mjs +23 -0
- package/dist/presets/laravel-vue-i18n.d.mts +6 -0
- package/dist/presets/laravel-vue-i18n.mjs +54 -0
- package/dist/presets/laravel.d.mts +6 -0
- package/dist/presets/laravel.mjs +23 -0
- package/dist/presets/lingui.d.mts +5 -0
- package/dist/presets/lingui.mjs +61 -0
- package/dist/presets/next-international.d.mts +5 -0
- package/dist/presets/next-international.mjs +27 -0
- package/dist/presets/next-intl.d.mts +5 -0
- package/dist/presets/next-intl.mjs +55 -0
- package/dist/presets/next-translate.d.mts +5 -0
- package/dist/presets/next-translate.mjs +53 -0
- package/dist/presets/ngx-translate.d.mts +5 -0
- package/dist/presets/ngx-translate.mjs +41 -0
- package/dist/presets/nuxt-i18n-micro.d.mts +5 -0
- package/dist/presets/nuxt-i18n-micro.mjs +81 -0
- package/dist/presets/nuxt-i18n.d.mts +5 -0
- package/dist/presets/nuxt-i18n.mjs +23 -0
- package/dist/presets/paraglide.d.mts +5 -0
- package/dist/presets/paraglide.mjs +38 -0
- package/dist/presets/php-gettext.d.mts +6 -0
- package/dist/presets/php-gettext.mjs +27 -0
- package/dist/presets/php-joomla.d.mts +6 -0
- package/dist/presets/php-joomla.mjs +22 -0
- package/dist/presets/polyglot.d.mts +5 -0
- package/dist/presets/polyglot.mjs +23 -0
- package/dist/presets/python-gettext.d.mts +6 -0
- package/dist/presets/python-gettext.mjs +44 -0
- package/dist/presets/react-i18next.d.mts +5 -0
- package/dist/presets/react-i18next.mjs +67 -0
- package/dist/presets/react-intl.d.mts +5 -0
- package/dist/presets/react-intl.mjs +47 -0
- package/dist/presets/ruby-i18n-js.d.mts +5 -0
- package/dist/presets/ruby-i18n-js.mjs +19 -0
- package/dist/presets/ruby-rails.d.mts +7 -0
- package/dist/presets/ruby-rails.mjs +44 -0
- package/dist/presets/solid-primitives-i18n.d.mts +5 -0
- package/dist/presets/solid-primitives-i18n.mjs +29 -0
- package/dist/presets/svelte-i18n.d.mts +5 -0
- package/dist/presets/svelte-i18n.mjs +33 -0
- package/dist/presets/svelte-i18next.d.mts +5 -0
- package/dist/presets/svelte-i18next.mjs +27 -0
- package/dist/presets/transloco.d.mts +5 -0
- package/dist/presets/transloco.mjs +44 -0
- package/dist/presets/typesafe-i18n.d.mts +6 -0
- package/dist/presets/typesafe-i18n.mjs +61 -0
- package/dist/presets/ui5.d.mts +5 -0
- package/dist/presets/ui5.mjs +35 -0
- package/dist/presets/vscode-extension.d.mts +5 -0
- package/dist/presets/vscode-extension.mjs +53 -0
- package/dist/presets/vue-i18n.d.mts +5 -0
- package/dist/presets/vue-i18n.mjs +59 -0
- package/dist/presets.d.mts +7 -54
- package/dist/presets.mjs +346 -1
- package/dist/reporters/github.d.mts +6 -0
- package/dist/reporters/github.mjs +27 -0
- package/dist/reporters/json.d.mts +6 -0
- package/dist/reporters/json.mjs +24 -0
- package/dist/reporters/markdown.d.mts +6 -0
- package/dist/reporters/markdown.mjs +37 -0
- package/dist/reporters/pretty.d.mts +6 -0
- package/dist/reporters/pretty.mjs +64 -0
- package/dist/reporters/sarif.d.mts +6 -0
- package/dist/reporters/sarif.mjs +48 -0
- package/dist/reporters.d.mts +5 -12
- package/dist/reporters.mjs +8 -218
- package/dist/reviews.d.mts +6 -0
- package/dist/reviews.mjs +35 -0
- package/dist/scanners/astro.d.mts +6 -0
- package/dist/scanners/astro.mjs +256 -0
- package/dist/scanners/js.d.mts +6 -0
- package/dist/scanners/js.mjs +33 -0
- package/dist/scanners/regex-scopes.d.mts +14 -0
- package/dist/scanners/regex-scopes.mjs +41 -0
- package/dist/scanners/regex.d.mts +9 -0
- package/dist/{regex-AYVhuSb6.mjs → scanners/regex.mjs} +2 -40
- package/dist/scanners/svelte.d.mts +6 -0
- package/dist/{scanners-DhnnVp7s.mjs → scanners/svelte.mjs} +3 -3
- package/dist/scanners/vue.d.mts +6 -0
- package/dist/scanners/vue.mjs +172 -0
- package/dist/scanners.d.mts +9 -50
- package/dist/scanners.mjs +36 -4
- package/dist/sfc-blocks-DaGKDZsb.mjs +213 -0
- package/dist/shared-C7ceptwi.mjs +55 -0
- package/dist/shared-D-tbsYmc.mjs +30 -0
- package/dist/shared-D4NJBKEP.mjs +17 -0
- package/dist/shared-Tq06G0gp.mjs +6 -0
- package/dist/shared-meYeVckh.mjs +11 -0
- package/dist/vue-compiler-0sfLIiiL.mjs +8 -0
- package/package.json +68 -3
- package/dist/formats-CFkM5co0.mjs +0 -1370
- package/dist/presets-Co5SlnKr.mjs +0 -1187
- package/dist/sfc-blocks-BqQ8m9rX.mjs +0 -161
- package/dist/yaml-plugin.d.mts +0 -8
- package/dist/yaml-plugin.mjs +0 -68
package/dist/reporters.mjs
CHANGED
|
@@ -1,221 +1,11 @@
|
|
|
1
|
-
import { createLineIndex, envelope, toWireDiagnostic } from "@x18n/core";
|
|
2
1
|
//#region src/reporters/index.ts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
/** Reporters by id. Each is also its own module, `@x18n/builtins/reporters/<id>`. */
|
|
3
|
+
const reporters = {
|
|
4
|
+
pretty: () => import("./reporters/pretty.mjs"),
|
|
5
|
+
json: () => import("./reporters/json.mjs"),
|
|
6
|
+
markdown: () => import("./reporters/markdown.mjs"),
|
|
7
|
+
github: () => import("./reporters/github.mjs"),
|
|
8
|
+
sarif: () => import("./reporters/sarif.mjs")
|
|
10
9
|
};
|
|
11
|
-
function paint(ctx, color, text) {
|
|
12
|
-
return ctx.color ? `\u001B[${codes[color]}m${text}\u001B[0m` : text;
|
|
13
|
-
}
|
|
14
|
-
function plural(count, word) {
|
|
15
|
-
return `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
16
|
-
}
|
|
17
|
-
function totals(inputs) {
|
|
18
|
-
return inputs.reduce((sum, input) => ({
|
|
19
|
-
errors: sum.errors + input.summary.errors,
|
|
20
|
-
warnings: sum.warnings + input.summary.warnings,
|
|
21
|
-
infos: sum.infos + input.summary.infos,
|
|
22
|
-
files: sum.files + input.summary.files
|
|
23
|
-
}), {
|
|
24
|
-
errors: 0,
|
|
25
|
-
warnings: 0,
|
|
26
|
-
infos: 0,
|
|
27
|
-
files: 0
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
const severityColor = {
|
|
31
|
-
error: "red",
|
|
32
|
-
warning: "yellow",
|
|
33
|
-
info: "blue",
|
|
34
|
-
hint: "gray"
|
|
35
|
-
};
|
|
36
|
-
/** The severity as a highlighted label, like ` ERROR `. */
|
|
37
|
-
function badge(ctx, severity) {
|
|
38
|
-
const label = ` ${severity.toUpperCase()} `;
|
|
39
|
-
return ctx.color ? `\u001B[1;7;${codes[severityColor[severity]]}m${label}\u001B[0m` : label;
|
|
40
|
-
}
|
|
41
|
-
/** The marked line with one line of context on each side, and `~` under the range. */
|
|
42
|
-
function codeFrame(text, start, end, severity, ctx) {
|
|
43
|
-
const index = createLineIndex(text);
|
|
44
|
-
const from = index.position(start);
|
|
45
|
-
const to = index.position(end);
|
|
46
|
-
const first = Math.max(0, from.line - 1);
|
|
47
|
-
const last = Math.min(index.lineCount - 1, from.line + 1);
|
|
48
|
-
const pad = String(last + 1).length;
|
|
49
|
-
const gutter = (n) => `${paint(ctx, "gray", n.padStart(pad))} ${paint(ctx, "gray", "│")} `;
|
|
50
|
-
const width = Math.max(1, (to.line === from.line ? to.character : index.line(from.line).length) - from.character);
|
|
51
|
-
const lines = [];
|
|
52
|
-
for (let n = first; n <= last; n++) {
|
|
53
|
-
const line = index.line(n);
|
|
54
|
-
lines.push(`${gutter(String(n + 1))}${n === from.line ? line : paint(ctx, "gray", line)}`);
|
|
55
|
-
if (n === from.line) lines.push(`${gutter("")}${" ".repeat(from.character)}${paint(ctx, severityColor[severity], "~".repeat(width))}`);
|
|
56
|
-
}
|
|
57
|
-
return lines.join("\n");
|
|
58
|
-
}
|
|
59
|
-
/** `path:line:col`, then the severity, message and check, a code frame, and a summary. */
|
|
60
|
-
const prettyReporter = {
|
|
61
|
-
id: "pretty",
|
|
62
|
-
report(inputs, ctx) {
|
|
63
|
-
const blocks = [];
|
|
64
|
-
for (const input of inputs) for (const d of input.diagnostics) {
|
|
65
|
-
const head = `${badge(ctx, d.severity)} ${d.message} ${paint(ctx, "gray", d.code)}`;
|
|
66
|
-
if (!d.location) {
|
|
67
|
-
blocks.push(head);
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
const text = input.text(d.location.path);
|
|
71
|
-
const position = createLineIndex(text ?? "").position(d.location.range.start);
|
|
72
|
-
const where = `${paint(ctx, "cyan", ctx.displayPath(d.location.path, input.project))}${paint(ctx, "gray", `:${position.line + 1}:${position.character + 1}`)}`;
|
|
73
|
-
const frame = text !== void 0 && d.location.range.end > d.location.range.start ? `\n${codeFrame(text, d.location.range.start, d.location.range.end, d.severity, ctx)}` : "";
|
|
74
|
-
blocks.push(`${where}\n${head}${frame}`);
|
|
75
|
-
}
|
|
76
|
-
const sum = totals(inputs);
|
|
77
|
-
if (sum.errors + sum.warnings === 0) blocks.push(paint(ctx, "gray", "Found no errors."));
|
|
78
|
-
else {
|
|
79
|
-
const parts = [
|
|
80
|
-
sum.errors ? paint(ctx, "red", `✖ ${plural(sum.errors, "error")}`) : "",
|
|
81
|
-
sum.warnings ? paint(ctx, "yellow", `⚠ ${plural(sum.warnings, "warning")}`) : "",
|
|
82
|
-
paint(ctx, "gray", plural(sum.files, "file"))
|
|
83
|
-
].filter(Boolean);
|
|
84
|
-
blocks.push(parts.join(" "));
|
|
85
|
-
}
|
|
86
|
-
return `${blocks.join("\n\n")}\n`;
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
/** The versioned JSON contract of `x18n check --json`. */
|
|
90
|
-
const jsonReporter = {
|
|
91
|
-
id: "json",
|
|
92
|
-
report(inputs) {
|
|
93
|
-
const diagnostics = inputs.flatMap((input) => input.diagnostics.map((d) => toWireDiagnostic(input.project, d, input.text)));
|
|
94
|
-
const data = { projects: inputs.map((input) => ({
|
|
95
|
-
name: input.project,
|
|
96
|
-
summary: input.summary,
|
|
97
|
-
coverage: input.coverage.map((c) => ({
|
|
98
|
-
locale: c.locale,
|
|
99
|
-
total: c.total,
|
|
100
|
-
translated: c.translated,
|
|
101
|
-
missing: c.missing.length,
|
|
102
|
-
empty: c.empty.length,
|
|
103
|
-
outdated: c.outdated.length
|
|
104
|
-
}))
|
|
105
|
-
})) };
|
|
106
|
-
return `${JSON.stringify(envelope("check", data, diagnostics), null, 2)}\n`;
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
function escapeCell(text) {
|
|
110
|
-
return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
111
|
-
}
|
|
112
|
-
/** Summary and tables for PR comments and agent context. */
|
|
113
|
-
const markdownReporter = {
|
|
114
|
-
id: "markdown",
|
|
115
|
-
report(inputs, ctx) {
|
|
116
|
-
const sum = totals(inputs);
|
|
117
|
-
const out = [
|
|
118
|
-
"## x18n check",
|
|
119
|
-
"",
|
|
120
|
-
`**${plural(sum.errors, "error")}**, ${plural(sum.warnings, "warning")} in ${plural(sum.files, "file")}.`,
|
|
121
|
-
""
|
|
122
|
-
];
|
|
123
|
-
for (const input of inputs) {
|
|
124
|
-
if (inputs.length > 1) out.push(`### ${input.project}`, "");
|
|
125
|
-
out.push("| Locale | Translated | Missing | Empty |", "| --- | --- | --- | --- |");
|
|
126
|
-
for (const c of input.coverage) out.push(`| ${c.locale} | ${c.translated}/${c.total} | ${c.missing.length} | ${c.empty.length} |`);
|
|
127
|
-
out.push("");
|
|
128
|
-
if (input.diagnostics.length === 0) continue;
|
|
129
|
-
out.push("| Severity | Check | Location | Message |", "| --- | --- | --- | --- |");
|
|
130
|
-
for (const d of input.diagnostics.slice(0, 200)) {
|
|
131
|
-
const line = d.location ? createLineIndex(input.text(d.location.path) ?? "").position(d.location.range.start).line + 1 : void 0;
|
|
132
|
-
const where = d.location ? `\`${ctx.displayPath(d.location.path, input.project)}:${line}\`` : "";
|
|
133
|
-
out.push(`| ${d.severity} | \`${d.code}\` | ${where} | ${escapeCell(d.message)} |`);
|
|
134
|
-
}
|
|
135
|
-
if (input.diagnostics.length > 200) out.push("", `_${input.diagnostics.length - 200} more not shown._`);
|
|
136
|
-
out.push("");
|
|
137
|
-
}
|
|
138
|
-
return `${out.join("\n")}\n`;
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
function escapeProperty(text) {
|
|
142
|
-
return text.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
143
|
-
}
|
|
144
|
-
function escapeData(text) {
|
|
145
|
-
return text.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
146
|
-
}
|
|
147
|
-
/** GitHub Actions workflow commands, which become annotations on the PR. */
|
|
148
|
-
const githubReporter = {
|
|
149
|
-
id: "github",
|
|
150
|
-
report(inputs, ctx) {
|
|
151
|
-
const out = [];
|
|
152
|
-
for (const input of inputs) for (const d of input.diagnostics) {
|
|
153
|
-
const level = d.severity === "error" ? "error" : d.severity === "warning" ? "warning" : "notice";
|
|
154
|
-
const props = [`title=${escapeProperty(`x18n ${d.code}`)}`];
|
|
155
|
-
if (d.location) {
|
|
156
|
-
const wire = toWireDiagnostic(input.project, d, input.text).range;
|
|
157
|
-
props.unshift(`file=${escapeProperty(ctx.displayPath(d.location.path, input.project))}`, `line=${wire.line}`, `col=${wire.column}`, `endLine=${wire.endLine}`, `endColumn=${wire.endColumn}`);
|
|
158
|
-
}
|
|
159
|
-
out.push(`::${level} ${props.join(",")}::${escapeData(d.message)}`);
|
|
160
|
-
}
|
|
161
|
-
return out.length ? `${out.join("\n")}\n` : "";
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
/** SARIF 2.1.0 for code scanning. */
|
|
165
|
-
const sarifReporter = {
|
|
166
|
-
id: "sarif",
|
|
167
|
-
report(inputs, ctx) {
|
|
168
|
-
const all = inputs.flatMap((input) => input.diagnostics.map((d) => ({
|
|
169
|
-
input,
|
|
170
|
-
d
|
|
171
|
-
})));
|
|
172
|
-
const rules = [...new Set(all.map(({ d }) => d.code))].sort();
|
|
173
|
-
const results = all.map(({ input, d }) => {
|
|
174
|
-
const wire = toWireDiagnostic(input.project, d, input.text);
|
|
175
|
-
return {
|
|
176
|
-
ruleId: d.code,
|
|
177
|
-
level: d.severity === "error" ? "error" : d.severity === "warning" ? "warning" : "note",
|
|
178
|
-
message: { text: d.message },
|
|
179
|
-
locations: d.location && wire.range ? [{ physicalLocation: {
|
|
180
|
-
artifactLocation: { uri: ctx.displayPath(d.location.path, input.project) },
|
|
181
|
-
region: {
|
|
182
|
-
startLine: wire.range.line,
|
|
183
|
-
startColumn: wire.range.column,
|
|
184
|
-
endLine: wire.range.endLine,
|
|
185
|
-
endColumn: wire.range.endColumn
|
|
186
|
-
}
|
|
187
|
-
} }] : []
|
|
188
|
-
};
|
|
189
|
-
});
|
|
190
|
-
const log = {
|
|
191
|
-
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
192
|
-
version: "2.1.0",
|
|
193
|
-
runs: [{
|
|
194
|
-
tool: { driver: {
|
|
195
|
-
name: "x18n",
|
|
196
|
-
informationUri: "https://x18n.antfu.dev",
|
|
197
|
-
rules: rules.map((id) => ({
|
|
198
|
-
id,
|
|
199
|
-
helpUri: `https://x18n.antfu.dev/checks/${id}`
|
|
200
|
-
}))
|
|
201
|
-
} },
|
|
202
|
-
results
|
|
203
|
-
}]
|
|
204
|
-
};
|
|
205
|
-
return `${JSON.stringify(log, null, 2)}\n`;
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
function reporters() {
|
|
209
|
-
return {
|
|
210
|
-
name: "x18n:reporters",
|
|
211
|
-
reporters: [
|
|
212
|
-
prettyReporter,
|
|
213
|
-
jsonReporter,
|
|
214
|
-
markdownReporter,
|
|
215
|
-
githubReporter,
|
|
216
|
-
sarifReporter
|
|
217
|
-
]
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
10
|
//#endregion
|
|
221
|
-
export {
|
|
11
|
+
export { reporters };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Plugin, ReviewStore } from "@x18n/core";
|
|
2
|
+
//#region src/reviews/index.d.ts
|
|
3
|
+
/** Reviews in a YAML file (`.x18n/reviews.yml`), tracked in git so they show up in pull requests. */
|
|
4
|
+
export declare const yamlReviewStore: ReviewStore;
|
|
5
|
+
export declare function reviews(): Plugin;
|
|
6
|
+
//#endregion
|
package/dist/reviews.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ReviewFileSchema, validate } from "@x18n/core";
|
|
2
|
+
import { parse, stringify } from "yaml";
|
|
3
|
+
//#region src/reviews/index.ts
|
|
4
|
+
const header = "# yaml-language-server: $schema=https://x18n.antfu.dev/schema/reviews.json\n";
|
|
5
|
+
/** Reviews in a YAML file (`.x18n/reviews.yml`), tracked in git so they show up in pull requests. */
|
|
6
|
+
const yamlReviewStore = {
|
|
7
|
+
id: "yaml",
|
|
8
|
+
parse(text) {
|
|
9
|
+
const result = validate(ReviewFileSchema, parse(text) ?? { x18n: 1 });
|
|
10
|
+
if (!result.success) throw new Error(result.issues.map((issue) => `${issue.path || "(root)"}: ${issue.message}`).join("; "));
|
|
11
|
+
return {
|
|
12
|
+
threads: result.output.threads ?? {},
|
|
13
|
+
sources: result.output.sources ?? {}
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
serialize(data) {
|
|
17
|
+
const file = {
|
|
18
|
+
x18n: 1,
|
|
19
|
+
...Object.keys(data.threads).length > 0 && { threads: data.threads },
|
|
20
|
+
...Object.keys(data.sources).length > 0 && { sources: data.sources }
|
|
21
|
+
};
|
|
22
|
+
return header + stringify(file, {
|
|
23
|
+
lineWidth: 0,
|
|
24
|
+
aliasDuplicateObjects: false
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function reviews() {
|
|
29
|
+
return {
|
|
30
|
+
name: "x18n:reviews",
|
|
31
|
+
reviewStores: [yamlReviewStore]
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { reviews, yamlReviewStore };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Plugin } from "@x18n/core";
|
|
2
|
+
//#region src/scanners/astro/index.d.ts
|
|
3
|
+
/** Astro components: the `---` frontmatter, `<script>` and template expressions through the `js` scanner. */
|
|
4
|
+
export declare function astro(): Plugin;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { astro as default };
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { n as loadParser, r as scanSource, t as isTextAttribute } from "../js-CV-ECNJG.mjs";
|
|
2
|
+
//#region src/scanners/astro/index.ts
|
|
3
|
+
const voidElements = /* @__PURE__ */ new Set([
|
|
4
|
+
"area",
|
|
5
|
+
"base",
|
|
6
|
+
"br",
|
|
7
|
+
"col",
|
|
8
|
+
"embed",
|
|
9
|
+
"hr",
|
|
10
|
+
"img",
|
|
11
|
+
"input",
|
|
12
|
+
"link",
|
|
13
|
+
"meta",
|
|
14
|
+
"source",
|
|
15
|
+
"track",
|
|
16
|
+
"wbr"
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Splits an Astro template into the parts the `js` scanner reads: top-level `{...}` expressions and
|
|
20
|
+
* `<script>` contents, plus text and text attributes as hard strings. Markup inside an expression
|
|
21
|
+
* (`{items.map(i => <li>{t(i)}</li>)}`) stays in the expression, which oxc parses as JSX.
|
|
22
|
+
*/
|
|
23
|
+
var AstroTemplate = class {
|
|
24
|
+
text;
|
|
25
|
+
textAttributes;
|
|
26
|
+
expressions = [];
|
|
27
|
+
scripts = [];
|
|
28
|
+
hardStrings = [];
|
|
29
|
+
constructor(text, textAttributes) {
|
|
30
|
+
this.text = text;
|
|
31
|
+
this.textAttributes = textAttributes;
|
|
32
|
+
}
|
|
33
|
+
/** Index after the first `token` from `i`, or the end of the text. */
|
|
34
|
+
after(i, token) {
|
|
35
|
+
const at = this.text.indexOf(token, i);
|
|
36
|
+
return at < 0 ? this.text.length : at + token.length;
|
|
37
|
+
}
|
|
38
|
+
/** Index after the run of characters matching `pattern` (sticky) at `i`. */
|
|
39
|
+
skip(pattern, i) {
|
|
40
|
+
pattern.lastIndex = i;
|
|
41
|
+
return pattern.test(this.text) ? pattern.lastIndex : i;
|
|
42
|
+
}
|
|
43
|
+
/** Markup from `i`. At the top level it records what it finds; nested in an expression it returns after its closing tag. */
|
|
44
|
+
markup(i, nested) {
|
|
45
|
+
const { text } = this;
|
|
46
|
+
let textStart = i;
|
|
47
|
+
while (i < text.length) {
|
|
48
|
+
const c = text[i];
|
|
49
|
+
const next = text[i + 1] ?? "";
|
|
50
|
+
if (c !== "{" && c !== "<") {
|
|
51
|
+
i++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
let end;
|
|
55
|
+
if (c === "{") end = this.expression(i, nested);
|
|
56
|
+
else if (text.startsWith("<!--", i)) end = this.after(i, "-->");
|
|
57
|
+
else if (next === "/" || next === "!" || next === "?") end = this.after(i, ">");
|
|
58
|
+
else if (/[a-z>]/i.test(next)) end = this.element(i, nested);
|
|
59
|
+
else {
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (!nested) this.textNode(textStart, i);
|
|
64
|
+
if (nested && c === "<" && next === "/") return end;
|
|
65
|
+
i = textStart = end;
|
|
66
|
+
}
|
|
67
|
+
if (!nested) this.textNode(textStart, i);
|
|
68
|
+
return i;
|
|
69
|
+
}
|
|
70
|
+
element(i, nested) {
|
|
71
|
+
const { text } = this;
|
|
72
|
+
const nameEnd = this.skip(/[^\s/>{]*/y, i + 1);
|
|
73
|
+
const name = text.slice(i + 1, nameEnd).toLowerCase();
|
|
74
|
+
let j = nameEnd;
|
|
75
|
+
while (j < text.length) {
|
|
76
|
+
j = this.skip(/\s*/y, j);
|
|
77
|
+
if (text[j] === ">" || text.startsWith("/>", j)) break;
|
|
78
|
+
if (text[j] === "{") {
|
|
79
|
+
j = this.expression(j, nested);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const attributeStart = j;
|
|
83
|
+
j = Math.max(this.skip(/[^\s=>/{]+/y, j), j + 1);
|
|
84
|
+
if (text[j] !== "=") continue;
|
|
85
|
+
const attribute = text.slice(attributeStart, j);
|
|
86
|
+
const quote = text[++j];
|
|
87
|
+
if (quote === "{") j = this.expression(j, nested);
|
|
88
|
+
else if (quote === "`") {
|
|
89
|
+
const end = this.template(j) + 1;
|
|
90
|
+
if (!nested) this.expressions.push({
|
|
91
|
+
start: j,
|
|
92
|
+
end
|
|
93
|
+
});
|
|
94
|
+
j = end;
|
|
95
|
+
} else if (quote === "\"" || quote === "'") {
|
|
96
|
+
const end = this.after(j + 1, quote);
|
|
97
|
+
const value = text.slice(j + 1, end - 1);
|
|
98
|
+
if (!nested && isTextAttribute(attribute, this.textAttributes) && /\p{L}/u.test(value)) this.hardStrings.push({
|
|
99
|
+
text: value,
|
|
100
|
+
range: {
|
|
101
|
+
start: attributeStart,
|
|
102
|
+
end
|
|
103
|
+
},
|
|
104
|
+
kind: "astro-attribute",
|
|
105
|
+
attribute
|
|
106
|
+
});
|
|
107
|
+
j = end;
|
|
108
|
+
} else j = this.skip(/[^\s>]*/y, j);
|
|
109
|
+
}
|
|
110
|
+
const selfClosing = text.startsWith("/>", j);
|
|
111
|
+
const end = Math.min(j + (selfClosing ? 2 : 1), text.length);
|
|
112
|
+
if (name === "script" || name === "style") {
|
|
113
|
+
const close = new RegExp(`</${name}`, "gi");
|
|
114
|
+
close.lastIndex = end;
|
|
115
|
+
const closeStart = close.exec(text)?.index ?? text.length;
|
|
116
|
+
if (name === "script" && !nested) this.scripts.push({
|
|
117
|
+
start: end,
|
|
118
|
+
end: closeStart
|
|
119
|
+
});
|
|
120
|
+
return this.after(closeStart, ">");
|
|
121
|
+
}
|
|
122
|
+
if (!nested || selfClosing || voidElements.has(name)) return end;
|
|
123
|
+
return this.markup(end, true);
|
|
124
|
+
}
|
|
125
|
+
/** `{...}` at `i`; returns the index after its `}`. */
|
|
126
|
+
expression(i, nested) {
|
|
127
|
+
const close = this.js(i + 1);
|
|
128
|
+
if (!nested) this.expressions.push({
|
|
129
|
+
start: i + 1,
|
|
130
|
+
end: close
|
|
131
|
+
});
|
|
132
|
+
return close + 1;
|
|
133
|
+
}
|
|
134
|
+
/** Index of the `}` that closes JS code starting at `i`, past strings, comments and JSX. */
|
|
135
|
+
js(i) {
|
|
136
|
+
const { text } = this;
|
|
137
|
+
let depth = 0;
|
|
138
|
+
let previous = "(";
|
|
139
|
+
for (; i < text.length; i++) {
|
|
140
|
+
const c = text[i];
|
|
141
|
+
if (c === "\"" || c === "'") i = this.literal(i, c);
|
|
142
|
+
else if (c === "`") i = this.template(i);
|
|
143
|
+
else if (text.startsWith("//", i)) i = this.after(i, "\n") - 1;
|
|
144
|
+
else if (text.startsWith("/*", i)) i = this.after(i, "*/") - 1;
|
|
145
|
+
else if (c === "/" && this.startsOperand(i, previous)) i = this.literal(i, "/");
|
|
146
|
+
else if (c === "{") depth++;
|
|
147
|
+
else if (c === "}") {
|
|
148
|
+
if (depth-- === 0) return i;
|
|
149
|
+
} else if (c === "<" && /[a-z>]/i.test(text[i + 1] ?? "") && this.startsOperand(i, previous)) {
|
|
150
|
+
i = this.element(i, true) - 1;
|
|
151
|
+
previous = ">";
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (!/\s/.test(c)) previous = c;
|
|
155
|
+
}
|
|
156
|
+
return text.length;
|
|
157
|
+
}
|
|
158
|
+
/** Whether `i` starts a value rather than continuing one: `<` opens JSX and `/` a regex there, after a value they are operators. */
|
|
159
|
+
startsOperand(i, previous) {
|
|
160
|
+
return !/[\w$)\]]/.test(previous) || /\breturn\s*$/.test(this.text.slice(Math.max(0, i - 10), i));
|
|
161
|
+
}
|
|
162
|
+
/** Index of the `quote` that closes the string or regex literal at `i`, past escapes and regex classes. */
|
|
163
|
+
literal(i, quote) {
|
|
164
|
+
const { text } = this;
|
|
165
|
+
let inClass = false;
|
|
166
|
+
for (i++; i < text.length; i++) {
|
|
167
|
+
const c = text[i];
|
|
168
|
+
if (c === "\\") i++;
|
|
169
|
+
else if (quote === "/" && (c === "[" || c === "]")) inClass = c === "[";
|
|
170
|
+
else if (c === quote && !inClass) return i;
|
|
171
|
+
}
|
|
172
|
+
return text.length;
|
|
173
|
+
}
|
|
174
|
+
/** Index of the backtick that closes the template literal at `i`. */
|
|
175
|
+
template(i) {
|
|
176
|
+
const { text } = this;
|
|
177
|
+
for (i++; i < text.length; i++) if (text[i] === "\\") i++;
|
|
178
|
+
else if (text[i] === "`") return i;
|
|
179
|
+
else if (text.startsWith("${", i)) i = this.js(i + 2);
|
|
180
|
+
return text.length;
|
|
181
|
+
}
|
|
182
|
+
textNode(start, end) {
|
|
183
|
+
const data = this.text.slice(start, end);
|
|
184
|
+
const text = data.trim();
|
|
185
|
+
if (!/\p{L}/u.test(text)) return;
|
|
186
|
+
const at = start + data.indexOf(text);
|
|
187
|
+
this.hardStrings.push({
|
|
188
|
+
text,
|
|
189
|
+
range: {
|
|
190
|
+
start: at,
|
|
191
|
+
end: at + text.length
|
|
192
|
+
},
|
|
193
|
+
kind: "astro-text"
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
/** Astro components: the `---` frontmatter, `<script>` and template expressions through the `js` scanner. */
|
|
198
|
+
function astro() {
|
|
199
|
+
return {
|
|
200
|
+
name: "x18n:scanner-astro",
|
|
201
|
+
scanners: [{
|
|
202
|
+
id: "astro",
|
|
203
|
+
extensions: ["astro"],
|
|
204
|
+
async scan(file, ctx) {
|
|
205
|
+
const parse = await loadParser();
|
|
206
|
+
const { text } = file;
|
|
207
|
+
const usages = [];
|
|
208
|
+
const keySlots = [];
|
|
209
|
+
const hardStrings = [];
|
|
210
|
+
const add = (range, filename, expression, reportHardStrings) => {
|
|
211
|
+
let result;
|
|
212
|
+
try {
|
|
213
|
+
result = scanSource(parse, text.slice(range.start, range.end), {
|
|
214
|
+
calls: ctx.calls,
|
|
215
|
+
ignoreCallees: ctx.config.extract.ignoreCallees,
|
|
216
|
+
textAttributes: ctx.config.extract.attributes,
|
|
217
|
+
offset: range.start,
|
|
218
|
+
filename,
|
|
219
|
+
expression,
|
|
220
|
+
hardStrings: reportHardStrings
|
|
221
|
+
});
|
|
222
|
+
} catch {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
usages.push(...result.usages);
|
|
226
|
+
keySlots.push(...result.keySlots ?? []);
|
|
227
|
+
hardStrings.push(...result.hardStrings ?? []);
|
|
228
|
+
};
|
|
229
|
+
let templateStart = 0;
|
|
230
|
+
const open = /^\s*---/.exec(text);
|
|
231
|
+
if (open) {
|
|
232
|
+
const close = /^---/gm;
|
|
233
|
+
close.lastIndex = open[0].length;
|
|
234
|
+
const end = close.exec(text)?.index ?? text.length;
|
|
235
|
+
add({
|
|
236
|
+
start: open[0].length,
|
|
237
|
+
end
|
|
238
|
+
}, "frontmatter.ts", false, true);
|
|
239
|
+
templateStart = end + 3;
|
|
240
|
+
}
|
|
241
|
+
const template = new AstroTemplate(text, ctx.config.extract.attributes);
|
|
242
|
+
template.markup(templateStart, false);
|
|
243
|
+
for (const range of template.scripts) add(range, "script.ts", false, false);
|
|
244
|
+
for (const range of template.expressions) add(range, "template.tsx", true, false);
|
|
245
|
+
hardStrings.push(...template.hardStrings);
|
|
246
|
+
return {
|
|
247
|
+
usages,
|
|
248
|
+
keySlots,
|
|
249
|
+
hardStrings
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
//#endregion
|
|
256
|
+
export { astro, astro as default };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { n as loadParser, r as scanSource } from "../js-CV-ECNJG.mjs";
|
|
2
|
+
//#region src/scanners/js/index.ts
|
|
3
|
+
/** The AST scanner for JS and TS, driven by call specs (`usage.calls`). */
|
|
4
|
+
function js() {
|
|
5
|
+
return {
|
|
6
|
+
name: "x18n:scanner-js",
|
|
7
|
+
scanners: [{
|
|
8
|
+
id: "js",
|
|
9
|
+
extensions: [
|
|
10
|
+
"js",
|
|
11
|
+
"jsx",
|
|
12
|
+
"mjs",
|
|
13
|
+
"cjs",
|
|
14
|
+
"ts",
|
|
15
|
+
"tsx",
|
|
16
|
+
"mts",
|
|
17
|
+
"cts"
|
|
18
|
+
],
|
|
19
|
+
async scan(file, ctx) {
|
|
20
|
+
const parse = await loadParser();
|
|
21
|
+
return scanSource(parse, file.text, {
|
|
22
|
+
calls: ctx.calls,
|
|
23
|
+
filename: file.path,
|
|
24
|
+
hardStrings: true,
|
|
25
|
+
ignoreCallees: ctx.config.extract.ignoreCallees,
|
|
26
|
+
textAttributes: ctx.config.extract.attributes
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}]
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { js as default, js };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Plugin } from "@x18n/core";
|
|
2
|
+
//#region src/scanners/regex-scopes/index.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Regions after each match of `pattern` (group 1 = namespace, or key prefix with `as: 'keyPrefix'`; or the
|
|
5
|
+
* named groups `namespace` and `keyPrefix`), up to the next match: i18n-ally `scopeRangeRegex`.
|
|
6
|
+
* With `wholeFile`, the first match scopes the whole file. Matches that capture nothing are skipped.
|
|
7
|
+
*/
|
|
8
|
+
export declare function regexScopes(pattern: string, id?: string, options?: {
|
|
9
|
+
readonly as?: "namespace" | "keyPrefix";
|
|
10
|
+
readonly extensions?: readonly string[];
|
|
11
|
+
readonly wholeFile?: boolean;
|
|
12
|
+
}): Plugin;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { regexScopes as default };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/scanners/regex-scopes/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Regions after each match of `pattern` (group 1 = namespace, or key prefix with `as: 'keyPrefix'`; or the
|
|
4
|
+
* named groups `namespace` and `keyPrefix`), up to the next match: i18n-ally `scopeRangeRegex`.
|
|
5
|
+
* With `wholeFile`, the first match scopes the whole file. Matches that capture nothing are skipped.
|
|
6
|
+
*/
|
|
7
|
+
function regexScopes(pattern, id = "scopes", options = {}) {
|
|
8
|
+
const re = new RegExp(pattern, "g");
|
|
9
|
+
return {
|
|
10
|
+
name: `x18n:scanner-${id}`,
|
|
11
|
+
scanners: [{
|
|
12
|
+
id,
|
|
13
|
+
extensions: options.extensions ?? ["*"],
|
|
14
|
+
scan(file) {
|
|
15
|
+
const found = [...file.text.matchAll(re)].map((match) => ({
|
|
16
|
+
match,
|
|
17
|
+
values: match.groups ? {
|
|
18
|
+
namespace: match.groups.namespace,
|
|
19
|
+
keyPrefix: match.groups.keyPrefix
|
|
20
|
+
} : options.as === "keyPrefix" ? { keyPrefix: match[1] } : { namespace: match[1] }
|
|
21
|
+
})).filter(({ values }) => values.namespace !== void 0 || values.keyPrefix !== void 0);
|
|
22
|
+
if (options.wholeFile) found.splice(1);
|
|
23
|
+
return {
|
|
24
|
+
usages: [],
|
|
25
|
+
scopes: found.map(({ match, values }, i) => ({
|
|
26
|
+
range: options.wholeFile ? {
|
|
27
|
+
start: 0,
|
|
28
|
+
end: file.text.length
|
|
29
|
+
} : {
|
|
30
|
+
start: match.index + match[0].length,
|
|
31
|
+
end: found[i + 1]?.match.index ?? file.text.length
|
|
32
|
+
},
|
|
33
|
+
...values
|
|
34
|
+
}))
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { regexScopes as default, regexScopes };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Plugin, RegexScannerSpec, UsageCandidate } from "@x18n/core";
|
|
2
|
+
//#region src/scanners/regex/index.d.ts
|
|
3
|
+
/** Turns a pattern with a `{key}` placeholder into a regex whose first group is the key. */
|
|
4
|
+
export declare function compileUsagePattern(pattern: string): RegExp;
|
|
5
|
+
export declare function scanWithRegex(text: string, regexes: readonly RegExp[]): UsageCandidate[];
|
|
6
|
+
/** The `regex` scanner over `usage.regex` specs, plus extra specs given here (YAML plugins). */
|
|
7
|
+
export declare function regex(specs?: readonly RegexScannerSpec[], id?: string): Plugin;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { regex as default };
|