@px-lsp/protocol 0.1.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.
Files changed (48) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +25 -0
  3. package/dist/arrays.d.ts +13 -0
  4. package/dist/arrays.js +19 -0
  5. package/dist/constants.d.ts +9 -0
  6. package/dist/constants.js +10 -0
  7. package/dist/descriptorMetadata.d.ts +51 -0
  8. package/dist/descriptorMetadata.js +98 -0
  9. package/dist/descriptorMod.d.ts +66 -0
  10. package/dist/descriptorMod.js +335 -0
  11. package/dist/errorLogParser.d.ts +33 -0
  12. package/dist/errorLogParser.js +125 -0
  13. package/dist/fsWalk.d.ts +20 -0
  14. package/dist/fsWalk.js +159 -0
  15. package/dist/locProperties.d.ts +13 -0
  16. package/dist/locProperties.js +46 -0
  17. package/dist/locRefs.d.ts +11 -0
  18. package/dist/locRefs.js +31 -0
  19. package/dist/modName.d.ts +6 -0
  20. package/dist/modName.js +53 -0
  21. package/dist/protocol.d.ts +1462 -0
  22. package/dist/protocol.js +201 -0
  23. package/dist/regex.d.ts +13 -0
  24. package/dist/regex.js +21 -0
  25. package/dist/suppression.d.ts +52 -0
  26. package/dist/suppression.js +173 -0
  27. package/dist/tigerParser.d.ts +28 -0
  28. package/dist/tigerParser.js +72 -0
  29. package/dist/translationCore.d.ts +26 -0
  30. package/dist/translationCore.js +162 -0
  31. package/dist/types.d.ts +82 -0
  32. package/dist/types.js +3 -0
  33. package/package.json +39 -0
  34. package/src/arrays.ts +16 -0
  35. package/src/constants.ts +12 -0
  36. package/src/descriptorMetadata.ts +101 -0
  37. package/src/descriptorMod.ts +354 -0
  38. package/src/errorLogParser.ts +136 -0
  39. package/src/fsWalk.ts +126 -0
  40. package/src/locProperties.ts +43 -0
  41. package/src/locRefs.ts +38 -0
  42. package/src/modName.ts +18 -0
  43. package/src/protocol.ts +1459 -0
  44. package/src/regex.ts +19 -0
  45. package/src/suppression.ts +178 -0
  46. package/src/tigerParser.ts +79 -0
  47. package/src/translationCore.ts +140 -0
  48. package/src/types.ts +90 -0
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scopeAtRequest = exports.dependenciesRequest = exports.eventValueOptionsRequest = exports.EVENT_VOCABULARY_MAX_VALUES = exports.EVENT_VOCABULARY_MAX_TOKENS = exports.eventVocabularyRequest = exports.eventBannerRequest = exports.eventGraphRequest = exports.guiWidgetEditRequest = exports.guiSourceEditRequest = exports.guiSaveValuesRequest = exports.GUI_PREVIEW_MAX = exports.guiPreviewRequest = exports.guiVocabularyRequest = exports.guiDependenciesRequest = exports.guiWidgetInfoRequest = exports.guiLayoutRequest = exports.guiTreeRequest = exports.eventDetailRequest = exports.overridesRequest = exports.locCoverageRequest = exports.modOverviewRequest = exports.progressNotification = exports.indexChangedNotification = exports.statusNotification = exports.lookupLocRequest = exports.indexStatsRequest = exports.reloadDocsRequest = exports.modFileChangedNotification = exports.configChangedNotification = exports.allClientCommandIds = exports.clientCommands = void 0;
4
+ // ---- client command ids ----------------------------------------------------
5
+ /**
6
+ * Client commands the server references in code actions and hover links (part
7
+ * of the wire contract: a client that implements one must register exactly
8
+ * this id and list it in {@link ParadoxClientCapabilities.commands}). They
9
+ * carry the "px." prefix: these are public extension command ids with shipped
10
+ * default keybindings. The prefix was renamed in the Paradox Toolkit rebrand
11
+ * and no fallback to the old ids is registered.
12
+ */
13
+ exports.clientCommands = {
14
+ editLocalization: "px.editLocalization",
15
+ openLocalizationSideBySide: "px.openLocalizationSideBySide",
16
+ showReferences: "px.showReferences",
17
+ };
18
+ /** Every id in {@link clientCommands}: what a fully capable client registers. */
19
+ exports.allClientCommandIds = Object.values(exports.clientCommands);
20
+ // ---- client -> server ------------------------------------------------------
21
+ /** Notification: settings changed; payload {@link ParadoxSettings}. */
22
+ exports.configChangedNotification = "paradox/configChanged";
23
+ /** Notification: a mod file changed on disk; payload {@link ModFileChangeParams}. */
24
+ exports.modFileChangedNotification = "paradox/modFileChanged";
25
+ /** Request: re-parse script_docs logs; payload {@link ReloadDocsParams} -> {@link ReloadDocsResult}. */
26
+ exports.reloadDocsRequest = "paradox/reloadDocs";
27
+ /** Request: index statistics; no payload -> {@link IndexStats}. */
28
+ exports.indexStatsRequest = "paradox/indexStats";
29
+ /** Request: look up localization entries for a key; {@link LookupLocParams} -> {@link LocEntryInfo}[].
30
+ * Mod entries shadow vanilla ones (the full list is returned, mod first). */
31
+ exports.lookupLocRequest = "paradox/lookupLoc";
32
+ // ---- server -> client ------------------------------------------------------
33
+ /** Notification: data health for the status bar; payload {@link StatusPayload}. */
34
+ exports.statusNotification = "paradox/status";
35
+ /** Notification: the definition index changed (debounced server-side); no payload.
36
+ * Overview views re-query on this signal. */
37
+ exports.indexChangedNotification = "paradox/indexChanged";
38
+ /** Notification: a long-running server phase started or finished; payload
39
+ * {@link ProgressPayload}. The status bar lists what is still loading, so a
40
+ * cold workspace says which step it is on instead of looking idle. No
41
+ * percentages: the phases are coarse and the client only shows their state. */
42
+ exports.progressNotification = "paradox/progress";
43
+ /** Request: mod content inventory; {@link ModScopedParams} -> {@link ModOverview}. */
44
+ exports.modOverviewRequest = "paradox/modOverview";
45
+ /** Request: localization coverage; {@link ModScopedParams} -> {@link LocCoverage}[]. */
46
+ exports.locCoverageRequest = "paradox/locCoverage";
47
+ /** Request: override/conflict map; {@link ModScopedParams} -> {@link OverrideInfo}[]. */
48
+ exports.overridesRequest = "paradox/overrides";
49
+ /** Request: full event detail for the graph inspector; {@link EventDetailParams} -> {@link EventDetail} | null. */
50
+ exports.eventDetailRequest = "paradox/eventDetail";
51
+ /** Request: GUI widget tree for a .gui document; {@link GuiTreeParams} -> {@link GuiTree}. */
52
+ exports.guiTreeRequest = "paradox/guiTree";
53
+ /**
54
+ * Request: rendered GUI layout for a .gui document;
55
+ * {@link GuiLayoutParams} -> {@link GuiLayoutResult}. Rectangles come from
56
+ * the measured layout engine (docs/gui-designer/calibration/spec.md), with
57
+ * templates/types resolved against the vanilla + mod gui tree.
58
+ */
59
+ exports.guiLayoutRequest = "paradox/guiLayout";
60
+ /**
61
+ * Request: the properties of ONE widget, as the layout engine resolved them;
62
+ * {@link GuiWidgetInfoParams} -> {@link GuiWidgetInfo}, null when the line
63
+ * carries no widget of its own (a node spliced in from a template or a type has
64
+ * no source here, the same answer `guiSourceEdit` refuses with).
65
+ *
66
+ * This is the designer inspector's READ side. It is a separate request rather
67
+ * than a field on {@link GuiLayoutNode} because it is per-SELECTION data: a
68
+ * vanilla window lays out 500+ widgets and carrying every widget's expanded
69
+ * property list on every layout push would multiply the payload for rows one
70
+ * widget at a time is ever shown.
71
+ */
72
+ exports.guiWidgetInfoRequest = "paradox/guiWidgetInfo";
73
+ /**
74
+ * Request: what a `.gui` document reaches on the SCRIPT side;
75
+ * {@link GuiDependenciesParams} -> {@link GuiDependenciesResult}. The forward
76
+ * half of the dependency surface; the reverse (script definition -> the .gui
77
+ * paths using it) is `paradox/dependencies` with `guiUses: true`, so both
78
+ * directions come out of the same scripted_gui link.
79
+ */
80
+ exports.guiDependenciesRequest = "paradox/guiDependencies";
81
+ /**
82
+ * Request: the widget names a designer palette may offer for THIS document;
83
+ * {@link GuiVocabularyParams} -> {@link GuiVocabularyResult}.
84
+ *
85
+ * Every name is harvested, never listed by hand: the bundled per-game widget
86
+ * schema (`data/<game>/guiSchema.json`, built from the vanilla `gui/` tree)
87
+ * plus the requested document's own `template` and `type` declarations. A
88
+ * palette entry is therefore always a widget the game knows.
89
+ */
90
+ exports.guiVocabularyRequest = "paradox/guiVocabulary";
91
+ /**
92
+ * Render-ready previews of palette entries: one instance of each entry laid
93
+ * out in a synthetic document that keeps the requested document's own
94
+ * declarations (so a local template previews with its real base). The
95
+ * result is an ordinary node tree the client draws with the same painter as
96
+ * the canvas. Entries a synthetic document cannot stand up (nothing to show,
97
+ * zero size, a type the store lacks) come back with `node: null` and a
98
+ * `reason`. Capped per request (GUI_PREVIEW_MAX); ask for the visible page.
99
+ */
100
+ exports.guiPreviewRequest = "paradox/guiPreview";
101
+ exports.GUI_PREVIEW_MAX = 48;
102
+ /**
103
+ * Preview values read out of a save game, so a designer draws
104
+ * `[GetPlayer.GetName]` as "Great Britain" instead of a placeholder chip.
105
+ *
106
+ * `values` is keyed by datafunction chain WITHOUT brackets, exactly the shape
107
+ * {@link GuiLayoutParams.previewValues} takes, so a client hands the answer
108
+ * straight back to the next layout request. A chain the save has no field for
109
+ * is absent: a preview shows what is knowable and never invents a value.
110
+ *
111
+ * The server streams the file and parses only the few blocks it needs (a big
112
+ * campaign runs ~115 MB), and caches the answer per file and mtime.
113
+ * Ironman and binary saves are refused with `error` set; melting them is a
114
+ * different tool.
115
+ */
116
+ exports.guiSaveValuesRequest = "paradox/guiSaveValues";
117
+ /**
118
+ * Request: source edits for a `.gui` designer gesture;
119
+ * {@link GuiSourceEditParams} -> {@link GuiSourceEditResult}, null when the
120
+ * request itself makes no sense (an unknown op). The server never writes: it
121
+ * returns offsets into the text it was handed and the host applies them, which
122
+ * keeps undo, dirty state and the live preview in the editor (EMBEDDING.md,
123
+ * host-owns-text).
124
+ *
125
+ * Every edit is surgical, over the exact span the source model recorded, so
126
+ * untouched bytes stay byte-identical: comments, CRLF, tabs-vs-spaces and
127
+ * single-line bodies all survive a write.
128
+ */
129
+ exports.guiSourceEditRequest = "paradox/guiSourceEdit";
130
+ /**
131
+ * Request: text edit for a preview interaction (drag / property change);
132
+ * {@link GuiWidgetEditParams} -> {@link GuiWidgetEditResult} (null when the
133
+ * widget or property cannot be edited). The client applies the offsets via
134
+ * WorkspaceEdit so undo and the live preview loop stay in the editor.
135
+ *
136
+ * @deprecated Use {@link guiSourceEditRequest} with a `setProperties` op. This
137
+ * is a thin alias over the same core, kept for hosts already wired to it: it
138
+ * can only write the `position`/`size` pair and returns one edit or null, so a
139
+ * refusal reaches the caller as a bare null with no reason attached.
140
+ */
141
+ exports.guiWidgetEditRequest = "paradox/guiWidgetEdit";
142
+ /** Request: event graph; {@link EventGraphParams} -> {@link EventGraph}. */
143
+ exports.eventGraphRequest = "paradox/eventGraph";
144
+ /**
145
+ * Request: the value sets an event editor may offer; {@link EventVocabularyParams}
146
+ * to {@link EventVocabularyResult}.
147
+ *
148
+ * Everything in the answer is DERIVED: the key lists come from the active
149
+ * profile's structure table, the field value sets from the schema's reference
150
+ * fields resolved through the definition index, and the effect/trigger lists
151
+ * from the user's script_docs (or the bundled wiki fallback). Nothing here is a
152
+ * hand-written name list, so a game patch that adds a theme or an effect shows
153
+ * up without a release.
154
+ */
155
+ /**
156
+ * Request: the illustration an event theme puts behind its window;
157
+ * {@link EventBannerParams} to {@link EventBannerResult}.
158
+ *
159
+ * Resolved through the game's own two hops (event_themes -> event_backgrounds),
160
+ * taking the last `background` block that carries no `trigger`, which is the
161
+ * file's own unconditional fallback. `texture` is the engine's mod-relative
162
+ * path, exactly as a `.gui` file would spell it, so a client resolves it with
163
+ * the same mod-then-game lookup it uses for any other texture. A theme that
164
+ * resolves to nothing answers `reason` instead: the caller is expected to say
165
+ * so rather than draw a picture that is not the event's.
166
+ */
167
+ exports.eventBannerRequest = "paradox/eventBanner";
168
+ exports.eventVocabularyRequest = "paradox/eventVocabulary";
169
+ /** Caps: an editor lists a page at a time, and these ride on every open. */
170
+ exports.EVENT_VOCABULARY_MAX_TOKENS = 600;
171
+ exports.EVENT_VOCABULARY_MAX_VALUES = 400;
172
+ /**
173
+ * Request: the value set a VALUE belongs to, resolved through the definition
174
+ * index; {@link EventValueOptionsParams} -> {@link EventValueOptionsResult} |
175
+ * null. The static vocabulary maps a KEY to its values, which only works where
176
+ * the schema knows the key's context (an event's or option's own fields). Deep
177
+ * inside an effect tree the same key name means something else (`type` in
178
+ * `random_secret` is a secret, not an event type), so there the editor asks
179
+ * about the value it already has: `secret_cultivator` is an indexed `secret`,
180
+ * and the answer is every secret the index knows. Null = the value resolves to
181
+ * nothing enumerable; the editor falls back to a free input.
182
+ */
183
+ exports.eventValueOptionsRequest = "paradox/eventValueOptions";
184
+ /**
185
+ * Request: dependency explorer for any indexed definition;
186
+ * {@link DependenciesParams} -> {@link DependenciesResult}. Cursor-driven
187
+ * (uri + position) or by name (optionally disambiguated by kind).
188
+ */
189
+ exports.dependenciesRequest = "paradox/dependencies";
190
+ /**
191
+ * Request: the inferred scope chain at a cursor position;
192
+ * {@link ScopeAtParams} -> {@link ScopeAtResult} | null. Answers for OPEN
193
+ * script documents only (the server reads the client's text, not the disk);
194
+ * null means "not open / not a script document", which a status bar renders as
195
+ * nothing rather than as an error.
196
+ *
197
+ * This is a read-out of the same inference completion, hover and inlay hints
198
+ * run at a position: it ranks and annotates, never diagnoses, and never
199
+ * asserts more than the derived link tables actually say.
200
+ */
201
+ exports.scopeAtRequest = "paradox/scopeAt";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * One correct copy of the regex-escape both sides need. It lived inline at five
3
+ * call sites and one of them had an extra backslash, which silently turned the
4
+ * escape into a no-op (it matched a metacharacter followed by two literal
5
+ * backslashes, so nothing was ever escaped).
6
+ */
7
+ /** Escape every regex metacharacter in `literal` so it matches itself. */
8
+ export declare function escapeRegExp(literal: string): string;
9
+ /**
10
+ * A pattern matching `name` only as a whole script identifier: not when it is
11
+ * a substring of a longer name, and not across a dot-chain segment boundary.
12
+ */
13
+ export declare function wholeNamePattern(name: string): string;
package/dist/regex.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ /**
3
+ * One correct copy of the regex-escape both sides need. It lived inline at five
4
+ * call sites and one of them had an extra backslash, which silently turned the
5
+ * escape into a no-op (it matched a metacharacter followed by two literal
6
+ * backslashes, so nothing was ever escaped).
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.escapeRegExp = escapeRegExp;
10
+ exports.wholeNamePattern = wholeNamePattern;
11
+ /** Escape every regex metacharacter in `literal` so it matches itself. */
12
+ function escapeRegExp(literal) {
13
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14
+ }
15
+ /**
16
+ * A pattern matching `name` only as a whole script identifier: not when it is
17
+ * a substring of a longer name, and not across a dot-chain segment boundary.
18
+ */
19
+ function wholeNamePattern(name) {
20
+ return `(?<![A-Za-z0-9_.\\-])${escapeRegExp(name)}(?![A-Za-z0-9_.\\-])`;
21
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Diagnostic suppression, shared by the server (own structural/loc diagnostics)
3
+ * and the client (tiger-forwarded reports) so one habit works across both tools.
4
+ *
5
+ * No `vscode` imports: plain data in, plain predicates out. Everything here is
6
+ * fail-soft — bad setting values or malformed comments are ignored, never thrown.
7
+ *
8
+ * Two mechanisms:
9
+ * 1. Settings: the diagnostics.ignore setting (diagnostic codes) and
10
+ * the diagnostics.ignorePatterns setting (globs on the workspace-relative path).
11
+ * 2. Inline comments: `# px:ignore <code…>` (same line) and
12
+ * `# px:ignore-next-line <code…>` (following line); a bare form with no
13
+ * codes suppresses every diagnostic on the target line. A trailing
14
+ * `-- <rationale>` is allowed and ignored.
15
+ */
16
+ /**
17
+ * Settings-driven filter. `ignore` matches a diagnostic's code (our stable
18
+ * codes, or tiger's `key`); `ignorePatterns` matches globs against the
19
+ * workspace-relative file path.
20
+ */
21
+ export interface DiagnosticIgnoreConfig {
22
+ /** Diagnostic codes to drop everywhere. */
23
+ ignore: string[];
24
+ /** Glob patterns matched against the workspace-relative (forward-slash) path. */
25
+ ignorePatterns: string[];
26
+ }
27
+ /** Normalize a raw settings array: strings only, trimmed, empties dropped. */
28
+ export declare function sanitizeStringList(value: unknown): string[];
29
+ /**
30
+ * Tiny `*`/`**` glob matcher (no dependency). `*` matches within a path segment,
31
+ * `**` matches across segments (including `/`). Matching is done on
32
+ * forward-slash paths and is case-insensitive (Windows-friendly). A pattern with
33
+ * no slash also matches against the basename, so `*.txt` works like a gitignore
34
+ * entry. Returns false on any malformed pattern.
35
+ */
36
+ export declare function globMatch(pattern: string, filePath: string): boolean;
37
+ /** True when a diagnostic with `code` in `filePath` should be dropped by settings. */
38
+ export declare function isIgnoredByConfig(cfg: DiagnosticIgnoreConfig, code: string | undefined, relPath: string): boolean;
39
+ /**
40
+ * Inline suppression map for a file, keyed by 0-based line number. A `null`
41
+ * value means "suppress every code on this line"; an array means "suppress only
42
+ * these codes". Built by scanning comment lines once when publishing.
43
+ */
44
+ export type InlineSuppressions = Map<number, string[] | null>;
45
+ /**
46
+ * Scan a document's text for `# px:ignore[-next-line] <code…>` comments.
47
+ * Cheap: only lines containing `px:ignore` are parsed. `-next-line` targets
48
+ * the following line; the plain form targets its own line.
49
+ */
50
+ export declare function scanInlineSuppressions(text: string): InlineSuppressions;
51
+ /** True when line `line` has an inline suppression covering `code`. */
52
+ export declare function isSuppressedInline(map: InlineSuppressions, line: number, code: string | undefined): boolean;
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ /**
3
+ * Diagnostic suppression, shared by the server (own structural/loc diagnostics)
4
+ * and the client (tiger-forwarded reports) so one habit works across both tools.
5
+ *
6
+ * No `vscode` imports: plain data in, plain predicates out. Everything here is
7
+ * fail-soft — bad setting values or malformed comments are ignored, never thrown.
8
+ *
9
+ * Two mechanisms:
10
+ * 1. Settings: the diagnostics.ignore setting (diagnostic codes) and
11
+ * the diagnostics.ignorePatterns setting (globs on the workspace-relative path).
12
+ * 2. Inline comments: `# px:ignore <code…>` (same line) and
13
+ * `# px:ignore-next-line <code…>` (following line); a bare form with no
14
+ * codes suppresses every diagnostic on the target line. A trailing
15
+ * `-- <rationale>` is allowed and ignored.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.sanitizeStringList = sanitizeStringList;
19
+ exports.globMatch = globMatch;
20
+ exports.isIgnoredByConfig = isIgnoredByConfig;
21
+ exports.scanInlineSuppressions = scanInlineSuppressions;
22
+ exports.isSuppressedInline = isSuppressedInline;
23
+ /** Normalize a raw settings array: strings only, trimmed, empties dropped. */
24
+ function sanitizeStringList(value) {
25
+ if (!Array.isArray(value))
26
+ return [];
27
+ const out = [];
28
+ for (const v of value) {
29
+ if (typeof v !== "string")
30
+ continue;
31
+ const t = v.trim();
32
+ if (t !== "")
33
+ out.push(t);
34
+ }
35
+ return out;
36
+ }
37
+ /**
38
+ * Tiny `*`/`**` glob matcher (no dependency). `*` matches within a path segment,
39
+ * `**` matches across segments (including `/`). Matching is done on
40
+ * forward-slash paths and is case-insensitive (Windows-friendly). A pattern with
41
+ * no slash also matches against the basename, so `*.txt` works like a gitignore
42
+ * entry. Returns false on any malformed pattern.
43
+ */
44
+ function globMatch(pattern, filePath) {
45
+ const p = pattern.replace(/\\/g, "/").toLowerCase();
46
+ const f = filePath.replace(/\\/g, "/").replace(/^\/+/, "").toLowerCase();
47
+ if (p === "")
48
+ return false;
49
+ try {
50
+ const re = new RegExp("^" + globToRegExpSource(p) + "$");
51
+ if (re.test(f))
52
+ return true;
53
+ // Slash-free patterns also match the basename (gitignore-style convenience).
54
+ if (!p.includes("/")) {
55
+ const base = f.slice(f.lastIndexOf("/") + 1);
56
+ return re.test(base);
57
+ }
58
+ return false;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ /** Translate a glob (already lowercased, forward-slashed) into a regex source. */
65
+ function globToRegExpSource(glob) {
66
+ let out = "";
67
+ for (let i = 0; i < glob.length; i++) {
68
+ const c = glob[i];
69
+ if (c === "*") {
70
+ if (glob[i + 1] === "*") {
71
+ // `**` — cross segments, optionally swallowing a trailing slash.
72
+ i++;
73
+ if (glob[i + 1] === "/") {
74
+ i++;
75
+ out += "(?:.*/)?";
76
+ }
77
+ else {
78
+ out += ".*";
79
+ }
80
+ }
81
+ else {
82
+ out += "[^/]*";
83
+ }
84
+ }
85
+ else if (c === "?") {
86
+ out += "[^/]";
87
+ }
88
+ else if ("\\^$.|+()[]{}".includes(c)) {
89
+ out += "\\" + c;
90
+ }
91
+ else {
92
+ out += c;
93
+ }
94
+ }
95
+ return out;
96
+ }
97
+ /** True when a diagnostic with `code` in `filePath` should be dropped by settings. */
98
+ function isIgnoredByConfig(cfg, code, relPath) {
99
+ if (code !== undefined && cfg.ignore.includes(code))
100
+ return true;
101
+ for (const pattern of cfg.ignorePatterns) {
102
+ if (globMatch(pattern, relPath))
103
+ return true;
104
+ }
105
+ return false;
106
+ }
107
+ const IGNORE_RE = /#\s*px:ignore(-next-line)?\b([^\n]*)/i;
108
+ /**
109
+ * Scan a document's text for `# px:ignore[-next-line] <code…>` comments.
110
+ * Cheap: only lines containing `px:ignore` are parsed. `-next-line` targets
111
+ * the following line; the plain form targets its own line.
112
+ */
113
+ function scanInlineSuppressions(text) {
114
+ const map = new Map();
115
+ if (!text.includes("px:ignore"))
116
+ return map;
117
+ const lines = text.split(/\r?\n/);
118
+ for (let i = 0; i < lines.length; i++) {
119
+ const line = lines[i];
120
+ // A comment can trail script on the same line; only look after the `#`.
121
+ const hash = line.indexOf("#");
122
+ if (hash < 0)
123
+ continue;
124
+ const m = IGNORE_RE.exec(line.slice(hash));
125
+ if (!m)
126
+ continue;
127
+ const target = m[1] ? i + 1 : i;
128
+ const codes = parseCodes(m[2]);
129
+ mergeSuppression(map, target, codes.length === 0 ? null : codes);
130
+ }
131
+ return map;
132
+ }
133
+ /**
134
+ * The codes following the marker, stopping at a `--` rationale. Writing a
135
+ * reason is the natural instinct, and without the cut-off every word of it
136
+ * parsed as a code — turning a suppression that matched everything into one
137
+ * that matched nothing, silently. Codes are kebab-case slugs
138
+ * (`unclosed-brace`, `loc-no-header`), so a leading `-` can only be the
139
+ * separator.
140
+ */
141
+ function parseCodes(rest) {
142
+ const out = [];
143
+ for (const token of rest.trim().split(/\s+/)) {
144
+ if (token === "")
145
+ continue;
146
+ if (token.startsWith("-"))
147
+ break; // `-- because the game allows it`
148
+ out.push(token);
149
+ }
150
+ return out;
151
+ }
152
+ function mergeSuppression(map, line, codes) {
153
+ const existing = map.get(line);
154
+ if (existing === undefined) {
155
+ map.set(line, codes);
156
+ return;
157
+ }
158
+ // `null` (suppress-all) wins; otherwise union the code lists.
159
+ if (existing === null || codes === null) {
160
+ map.set(line, null);
161
+ return;
162
+ }
163
+ map.set(line, [...existing, ...codes]);
164
+ }
165
+ /** True when line `line` has an inline suppression covering `code`. */
166
+ function isSuppressedInline(map, line, code) {
167
+ if (!map.has(line))
168
+ return false;
169
+ const codes = map.get(line) ?? null;
170
+ if (codes === null)
171
+ return true; // bare `# px:ignore` suppresses all
172
+ return code !== undefined && codes.includes(code);
173
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Parser for tiger `--json` reports (the Paradox script validator family).
3
+ *
4
+ * Kept separate from the process management in tiger.ts so it stays free of
5
+ * `vscode` imports and defensively tolerant of format drift between tiger
6
+ * releases: unknown fields are ignored, malformed entries are skipped.
7
+ */
8
+ export interface TigerLocation {
9
+ path: string;
10
+ fullpath?: string;
11
+ /** 1-based, may be missing for file-level reports. */
12
+ linenr?: number;
13
+ /** 1-based. */
14
+ column?: number;
15
+ length?: number;
16
+ tag?: string;
17
+ }
18
+ export interface TigerReport {
19
+ severity: string;
20
+ /** tiger also rates how sure it is: weak | reasonable | strong. */
21
+ confidence?: string;
22
+ key: string;
23
+ message: string;
24
+ info?: string;
25
+ locations: TigerLocation[];
26
+ }
27
+ /** Parse tiger's JSON output. Returns null if no JSON array can be found at all. */
28
+ export declare function parseTigerJson(stdout: string): TigerReport[] | null;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ /**
3
+ * Parser for tiger `--json` reports (the Paradox script validator family).
4
+ *
5
+ * Kept separate from the process management in tiger.ts so it stays free of
6
+ * `vscode` imports and defensively tolerant of format drift between tiger
7
+ * releases: unknown fields are ignored, malformed entries are skipped.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.parseTigerJson = parseTigerJson;
11
+ /** Parse tiger's JSON output. Returns null if no JSON array can be found at all. */
12
+ function parseTigerJson(stdout) {
13
+ let raw;
14
+ try {
15
+ raw = JSON.parse(stdout);
16
+ }
17
+ catch {
18
+ // tiger may print progress noise before the JSON; try from the first '['.
19
+ const start = stdout.indexOf("[");
20
+ if (start < 0)
21
+ return null;
22
+ try {
23
+ raw = JSON.parse(stdout.slice(start));
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ if (!Array.isArray(raw))
30
+ return null;
31
+ const reports = [];
32
+ for (const entry of raw) {
33
+ if (typeof entry !== "object" || entry === null)
34
+ continue;
35
+ const e = entry;
36
+ const message = typeof e.message === "string" ? e.message : null;
37
+ const locationsRaw = Array.isArray(e.locations) ? e.locations : [];
38
+ if (message === null)
39
+ continue;
40
+ const locations = [];
41
+ for (const locRaw of locationsRaw) {
42
+ if (typeof locRaw !== "object" || locRaw === null)
43
+ continue;
44
+ const l = locRaw;
45
+ const p = typeof l.fullpath === "string" ? l.fullpath : typeof l.path === "string" ? l.path : null;
46
+ if (p === null)
47
+ continue;
48
+ const loc = { path: typeof l.path === "string" ? l.path : p };
49
+ if (typeof l.fullpath === "string")
50
+ loc.fullpath = l.fullpath;
51
+ const linenr = l.linenr ?? l.line;
52
+ if (typeof linenr === "number")
53
+ loc.linenr = linenr;
54
+ if (typeof l.column === "number")
55
+ loc.column = l.column;
56
+ if (typeof l.length === "number")
57
+ loc.length = l.length;
58
+ if (typeof l.tag === "string")
59
+ loc.tag = l.tag;
60
+ locations.push(loc);
61
+ }
62
+ reports.push({
63
+ severity: typeof e.severity === "string" ? e.severity : "warning",
64
+ confidence: typeof e.confidence === "string" ? e.confidence : undefined,
65
+ key: typeof e.key === "string" ? e.key : "unknown",
66
+ message,
67
+ info: typeof e.info === "string" ? e.info : undefined,
68
+ locations,
69
+ });
70
+ }
71
+ return reports;
72
+ }
@@ -0,0 +1,26 @@
1
+ export declare const LOC_LANGUAGES: string[];
2
+ /** Language of a loc file, from its `_l_<lang>.yml` suffix or a path segment. */
3
+ export declare function detectLocFileLanguage(filePath: string): string | null;
4
+ /**
5
+ * Where the translated counterpart of `srcFile` lives: language path segments
6
+ * and the `_l_<lang>` filename marker are retargeted. Returns null when the
7
+ * path carries no language marker at all.
8
+ */
9
+ export declare function retargetLocPath(srcFile: string, sourceLang: string, targetLang: string): string | null;
10
+ /**
11
+ * A translation skeleton: the source file's structure (comments and blank
12
+ * lines preserved — they are context for the translator) with the language
13
+ * header switched and every value BLANKED; the source text stays visible as
14
+ * an inline `# english: …` comment so the translator sees it right there
15
+ * without it leaking into the game as a fake translation.
16
+ */
17
+ export declare function buildTranslation(sourceContent: string, targetLang: string, sourceLang?: string): string;
18
+ export interface MergeResult {
19
+ content: string;
20
+ added: number;
21
+ }
22
+ /**
23
+ * Add entries that exist in the source but not yet in the target, appended at
24
+ * the end under a marker comment. Existing target lines are never touched.
25
+ */
26
+ export declare function mergeTranslation(targetContent: string, sourceContent: string, sourceLang: string): MergeResult;