@hominis/fireforge 0.10.1 → 0.11.1
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 +93 -1
- package/README.md +125 -238
- package/dist/bin/fireforge.js +26 -0
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +131 -52
- package/dist/src/commands/bootstrap.js +6 -2
- package/dist/src/commands/build.js +4 -2
- package/dist/src/commands/discard.js +16 -4
- package/dist/src/commands/doctor-furnace.d.ts +8 -0
- package/dist/src/commands/doctor-furnace.js +422 -0
- package/dist/src/commands/doctor.d.ts +115 -0
- package/dist/src/commands/doctor.js +327 -258
- package/dist/src/commands/download.js +16 -1
- package/dist/src/commands/export-all.js +15 -0
- package/dist/src/commands/export-flow.d.ts +91 -0
- package/dist/src/commands/export-flow.js +344 -0
- package/dist/src/commands/export.js +151 -5
- package/dist/src/commands/furnace/apply.d.ts +3 -2
- package/dist/src/commands/furnace/apply.js +169 -36
- package/dist/src/commands/furnace/create.js +162 -52
- package/dist/src/commands/furnace/deploy.js +156 -144
- package/dist/src/commands/furnace/diff.d.ts +8 -4
- package/dist/src/commands/furnace/diff.js +142 -73
- package/dist/src/commands/furnace/index.d.ts +6 -2
- package/dist/src/commands/furnace/index.js +76 -25
- package/dist/src/commands/furnace/init.d.ts +11 -0
- package/dist/src/commands/furnace/init.js +76 -0
- package/dist/src/commands/furnace/list.d.ts +4 -1
- package/dist/src/commands/furnace/list.js +35 -3
- package/dist/src/commands/furnace/override.d.ts +8 -0
- package/dist/src/commands/furnace/override.js +216 -26
- package/dist/src/commands/furnace/preview.js +184 -30
- package/dist/src/commands/furnace/refresh.d.ts +10 -0
- package/dist/src/commands/furnace/refresh.js +268 -0
- package/dist/src/commands/furnace/remove.js +285 -89
- package/dist/src/commands/furnace/rename.d.ts +5 -0
- package/dist/src/commands/furnace/rename.js +308 -0
- package/dist/src/commands/furnace/scan.d.ts +4 -1
- package/dist/src/commands/furnace/scan.js +72 -11
- package/dist/src/commands/furnace/status.js +85 -20
- package/dist/src/commands/furnace/sync.d.ts +12 -0
- package/dist/src/commands/furnace/sync.js +77 -0
- package/dist/src/commands/furnace/validate.d.ts +4 -1
- package/dist/src/commands/furnace/validate.js +99 -3
- package/dist/src/commands/furnace/validation-output.d.ts +24 -1
- package/dist/src/commands/furnace/validation-output.js +93 -1
- package/dist/src/commands/import.js +37 -4
- package/dist/src/commands/lint.js +11 -2
- package/dist/src/commands/manifest.d.ts +39 -0
- package/dist/src/commands/manifest.js +59 -0
- package/dist/src/commands/patch/delete.d.ts +28 -0
- package/dist/src/commands/patch/delete.js +209 -0
- package/dist/src/commands/patch/index.d.ts +17 -0
- package/dist/src/commands/patch/index.js +25 -0
- package/dist/src/commands/patch/reorder.d.ts +30 -0
- package/dist/src/commands/patch/reorder.js +377 -0
- package/dist/src/commands/re-export-files.d.ts +17 -0
- package/dist/src/commands/re-export-files.js +177 -0
- package/dist/src/commands/re-export.js +44 -0
- package/dist/src/commands/rebase/abort.d.ts +1 -1
- package/dist/src/commands/rebase/abort.js +12 -3
- package/dist/src/commands/rebase/confirm.d.ts +3 -3
- package/dist/src/commands/rebase/confirm.js +4 -4
- package/dist/src/commands/rebase/index.js +13 -4
- package/dist/src/commands/reset.js +20 -4
- package/dist/src/commands/run.js +46 -1
- package/dist/src/commands/setup-support.js +6 -5
- package/dist/src/commands/status.js +97 -6
- package/dist/src/commands/test.js +5 -37
- package/dist/src/commands/verify.d.ts +31 -0
- package/dist/src/commands/verify.js +126 -0
- package/dist/src/core/build-prepare.js +40 -16
- package/dist/src/core/destructive.d.ts +96 -0
- package/dist/src/core/destructive.js +137 -0
- package/dist/src/core/diff-hunks.d.ts +73 -0
- package/dist/src/core/diff-hunks.js +268 -0
- package/dist/src/core/firefox.d.ts +1 -1
- package/dist/src/core/firefox.js +1 -1
- package/dist/src/core/furnace-apply-helpers.d.ts +89 -6
- package/dist/src/core/furnace-apply-helpers.js +302 -57
- package/dist/src/core/furnace-apply-output.d.ts +16 -0
- package/dist/src/core/furnace-apply-output.js +57 -0
- package/dist/src/core/furnace-apply.d.ts +21 -3
- package/dist/src/core/furnace-apply.js +260 -29
- package/dist/src/core/furnace-checksum-utils.d.ts +4 -0
- package/dist/src/core/furnace-checksum-utils.js +24 -0
- package/dist/src/core/furnace-config.d.ts +28 -1
- package/dist/src/core/furnace-config.js +180 -17
- package/dist/src/core/furnace-constants.d.ts +22 -0
- package/dist/src/core/furnace-constants.js +36 -0
- package/dist/src/core/furnace-graph-utils.d.ts +11 -0
- package/dist/src/core/furnace-graph-utils.js +94 -0
- package/dist/src/core/furnace-operation.d.ts +108 -0
- package/dist/src/core/furnace-operation.js +220 -0
- package/dist/src/core/furnace-refresh.d.ts +20 -0
- package/dist/src/core/furnace-refresh.js +118 -0
- package/dist/src/core/furnace-registration-ast.d.ts +5 -0
- package/dist/src/core/furnace-registration-ast.js +134 -4
- package/dist/src/core/furnace-registration-remove.d.ts +25 -3
- package/dist/src/core/furnace-registration-remove.js +196 -62
- package/dist/src/core/furnace-registration-validate.d.ts +13 -1
- package/dist/src/core/furnace-registration-validate.js +15 -3
- package/dist/src/core/furnace-registration.d.ts +27 -4
- package/dist/src/core/furnace-registration.js +93 -11
- package/dist/src/core/furnace-rollback.d.ts +11 -0
- package/dist/src/core/furnace-rollback.js +78 -7
- package/dist/src/core/furnace-scanner.d.ts +8 -2
- package/dist/src/core/furnace-scanner.js +152 -55
- package/dist/src/core/furnace-stories.js +7 -5
- package/dist/src/core/furnace-validate-accessibility.js +7 -1
- package/dist/src/core/furnace-validate-compatibility.d.ts +1 -1
- package/dist/src/core/furnace-validate-compatibility.js +85 -1
- package/dist/src/core/furnace-validate-helpers.d.ts +4 -0
- package/dist/src/core/furnace-validate-helpers.js +31 -0
- package/dist/src/core/furnace-validate-registration.d.ts +17 -2
- package/dist/src/core/furnace-validate-registration.js +73 -3
- package/dist/src/core/furnace-validate-structure.d.ts +10 -2
- package/dist/src/core/furnace-validate-structure.js +45 -3
- package/dist/src/core/furnace-validate.d.ts +10 -1
- package/dist/src/core/furnace-validate.js +80 -6
- package/dist/src/core/furnace-version-drift.d.ts +55 -0
- package/dist/src/core/furnace-version-drift.js +101 -0
- package/dist/src/core/git-file-ops.d.ts +8 -0
- package/dist/src/core/git-file-ops.js +19 -6
- package/dist/src/core/lint-projection.d.ts +25 -0
- package/dist/src/core/lint-projection.js +44 -0
- package/dist/src/core/mach.d.ts +4 -2
- package/dist/src/core/mach.js +17 -2
- package/dist/src/core/markdown-table.d.ts +104 -0
- package/dist/src/core/markdown-table.js +266 -0
- package/dist/src/core/ownership-table.d.ts +53 -0
- package/dist/src/core/ownership-table.js +144 -0
- package/dist/src/core/patch-apply.d.ts +17 -3
- package/dist/src/core/patch-apply.js +86 -8
- package/dist/src/core/patch-export.d.ts +119 -5
- package/dist/src/core/patch-export.js +183 -25
- package/dist/src/core/patch-lint-cross.d.ts +195 -0
- package/dist/src/core/patch-lint-cross.js +428 -0
- package/dist/src/core/patch-lint-diff.d.ts +33 -0
- package/dist/src/core/patch-lint-diff.js +84 -0
- package/dist/src/core/patch-lint.d.ts +2 -4
- package/dist/src/core/patch-lint.js +12 -50
- package/dist/src/core/patch-lock.js +2 -1
- package/dist/src/core/patch-manifest-io.d.ts +102 -1
- package/dist/src/core/patch-manifest-io.js +270 -2
- package/dist/src/core/patch-manifest-query.d.ts +1 -1
- package/dist/src/core/patch-manifest-query.js +1 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -1
- package/dist/src/core/patch-transform.d.ts +12 -0
- package/dist/src/core/patch-transform.js +21 -7
- package/dist/src/core/token-manager.js +67 -69
- package/dist/src/core/wire-destroy.js +6 -3
- package/dist/src/core/wire-init.js +10 -4
- package/dist/src/core/wire-subscript.js +9 -3
- package/dist/src/core/wire-utils.d.ts +52 -5
- package/dist/src/core/wire-utils.js +69 -6
- package/dist/src/errors/base.d.ts +20 -0
- package/dist/src/errors/base.js +24 -0
- package/dist/src/errors/furnace.js +7 -1
- package/dist/src/errors/rebase.js +6 -1
- package/dist/src/types/commands/index.d.ts +1 -1
- package/dist/src/types/commands/options.d.ts +125 -4
- package/dist/src/types/commands/patches.d.ts +11 -1
- package/dist/src/types/config.d.ts +1 -1
- package/dist/src/types/furnace.d.ts +55 -1
- package/dist/src/utils/fs.d.ts +12 -0
- package/dist/src/utils/fs.js +30 -1
- package/dist/src/utils/package-root.d.ts +5 -0
- package/dist/src/utils/package-root.js +12 -0
- package/dist/src/utils/process.js +9 -4
- package/dist/src/utils/validation.d.ts +20 -2
- package/dist/src/utils/validation.js +26 -3
- package/package.json +1 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-patch lint infrastructure: the queue context builder, the
|
|
3
|
+
* duplicate-new-file-creation and forward-import rules, the
|
|
4
|
+
* forward-import ignore-marker, and the per-specifier extractor that
|
|
5
|
+
* powers the forward-import rule.
|
|
6
|
+
*
|
|
7
|
+
* Separated from `patch-lint.ts` so the per-patch and cross-patch rule
|
|
8
|
+
* bodies stay within the project's per-file line budget. `patch-lint.ts`
|
|
9
|
+
* re-exports the public surface so callers continue to import from a
|
|
10
|
+
* single module.
|
|
11
|
+
*/
|
|
12
|
+
import type { PatchLintIssue, PatchMetadata } from '../types/commands/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* One patch's contribution to {@link PatchQueueContext}.
|
|
15
|
+
*
|
|
16
|
+
* Rules receive a flat view of every patch's metadata, raw diff, and the
|
|
17
|
+
* content of any files the patch newly creates. Reading each patch once up
|
|
18
|
+
* front lets rules operate in O(patches) without re-reading .patch files.
|
|
19
|
+
*/
|
|
20
|
+
export interface PatchQueueEntry {
|
|
21
|
+
/** Filename on disk and in the manifest. */
|
|
22
|
+
filename: string;
|
|
23
|
+
/** Order number from the manifest (or filename prefix fallback). */
|
|
24
|
+
order: number;
|
|
25
|
+
/** Manifest metadata. Null when the patch file exists but has no entry. */
|
|
26
|
+
metadata: PatchMetadata | null;
|
|
27
|
+
/** Raw unified-diff content of the patch body. */
|
|
28
|
+
diff: string;
|
|
29
|
+
/**
|
|
30
|
+
* Map from newly-created file path → the file content the patch would
|
|
31
|
+
* produce. Populated only for files the patch creates with
|
|
32
|
+
* `new file mode` + `--- /dev/null`. Modifications to existing files
|
|
33
|
+
* are not indexed here.
|
|
34
|
+
*/
|
|
35
|
+
newFiles: Map<string, string>;
|
|
36
|
+
/**
|
|
37
|
+
* Map from existing-file path → concatenated added lines from the patch's
|
|
38
|
+
* hunks against that file (joined with `\n`). Populated for files the
|
|
39
|
+
* patch *modifies* (i.e. paths that show up in the diff without a
|
|
40
|
+
* `new file mode` marker). The forward-import rule and `patch delete`
|
|
41
|
+
* dependency scan use this to detect imports added into pre-existing
|
|
42
|
+
* files — a failure mode the newFiles map cannot represent because it
|
|
43
|
+
* only tracks creations.
|
|
44
|
+
*/
|
|
45
|
+
modifiedFileAdditions: Map<string, string>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Queue-wide context passed to cross-patch lint rules.
|
|
49
|
+
*
|
|
50
|
+
* "Projected" means rules receive a potentially hypothetical view of the
|
|
51
|
+
* queue — the caller may have already applied a planned delete, reorder, or
|
|
52
|
+
* re-export to the entries before calling the rule. This lets
|
|
53
|
+
* `patch reorder`, `patch delete`, `export --order`, and `re-export --files`
|
|
54
|
+
* run the same cross-patch checks they would hit on a real run, without
|
|
55
|
+
* mutating disk first.
|
|
56
|
+
*/
|
|
57
|
+
export interface PatchQueueContext {
|
|
58
|
+
/** Entries in application order (lowest `order` first). */
|
|
59
|
+
entries: PatchQueueEntry[];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Builds a {@link PatchQueueContext} by reading every .patch file in the
|
|
63
|
+
* directory and extracting new-file content for each creation.
|
|
64
|
+
*
|
|
65
|
+
* Reads manifest metadata best-effort: if the manifest is missing or a patch
|
|
66
|
+
* file has no metadata entry, the context still populates the entry from the
|
|
67
|
+
* filename prefix so cross-patch rules can operate on a drift state. (A
|
|
68
|
+
* separate consistency check — see `patches verify` — is responsible for
|
|
69
|
+
* reporting the drift as its own error.)
|
|
70
|
+
*
|
|
71
|
+
* @param patchesDir - Path to the patches directory
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildPatchQueueContext(patchesDir: string): Promise<PatchQueueContext>;
|
|
74
|
+
/**
|
|
75
|
+
* Returns the raw `path → patches[]` map of files created in `new file
|
|
76
|
+
* mode` by at least one patch in the queue. Paths created by only one
|
|
77
|
+
* patch are also included so callers can distinguish "no creator" from
|
|
78
|
+
* "exactly one creator" without re-scanning the diffs.
|
|
79
|
+
*
|
|
80
|
+
* Split out from {@link lintPatchQueueDuplicateCreations} so
|
|
81
|
+
* `status --ownership` (and any future caller that wants ownership
|
|
82
|
+
* without a rendered PatchLintIssue) can consume the same structured
|
|
83
|
+
* data the rule itself relies on. Previously status had to parse the
|
|
84
|
+
* rule's human-readable message to recover the patch list, which was
|
|
85
|
+
* both fragile and made the lint message format part of an implicit
|
|
86
|
+
* contract.
|
|
87
|
+
*
|
|
88
|
+
* @param ctx - Pre-built queue context
|
|
89
|
+
*/
|
|
90
|
+
export declare function collectNewFileCreatorsByPath(ctx: PatchQueueContext): Map<string, string[]>;
|
|
91
|
+
/**
|
|
92
|
+
* Cross-patch lint rule: the same path is newly created (`--- /dev/null →
|
|
93
|
+
* +++ b/path`) by more than one patch. This is the failure mode that
|
|
94
|
+
* motivated the rule — Hominis landed three patches each trying to create
|
|
95
|
+
* the same file, and the error surfaced only when import rolled back
|
|
96
|
+
* mid-apply.
|
|
97
|
+
*
|
|
98
|
+
* Reports one error per conflicting path, naming every patch that creates
|
|
99
|
+
* the path so the operator can pick the correct fix (`patch delete` or
|
|
100
|
+
* `re-export --files`).
|
|
101
|
+
*/
|
|
102
|
+
export declare function lintPatchQueueDuplicateCreations(ctx: PatchQueueContext): PatchLintIssue[];
|
|
103
|
+
/**
|
|
104
|
+
* Returns true when a path looks like a JS module/subscript the
|
|
105
|
+
* forward-import rule should scan.
|
|
106
|
+
*/
|
|
107
|
+
export declare function isForwardImportableFile(path: string): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Regex-level extractor for module specifiers in ES module / subscript files.
|
|
110
|
+
*
|
|
111
|
+
* Intentionally conservative. Catches:
|
|
112
|
+
* - `import ... from "specifier"` (ES module static imports)
|
|
113
|
+
* - `import "specifier"` (side-effect imports — the `from`
|
|
114
|
+
* clause is optional in the regex)
|
|
115
|
+
* - `import("specifier")` (dynamic imports)
|
|
116
|
+
* - ChromeUtils.defineESModuleGetters(obj, { Name: "specifier", ... })
|
|
117
|
+
*
|
|
118
|
+
* Returns the raw specifier strings — callers should take the leaf basename
|
|
119
|
+
* to match against the newFileIndex, because we do not resolve `resource://`
|
|
120
|
+
* URLs to engine file paths.
|
|
121
|
+
*/
|
|
122
|
+
export declare function extractImportSpecifiers(source: string): string[];
|
|
123
|
+
/**
|
|
124
|
+
* Internal form of {@link extractImportSpecifiers} that also returns the
|
|
125
|
+
* (0-indexed) line number where each specifier was found. Used by the
|
|
126
|
+
* forward-import rule so it can correlate specifiers against the
|
|
127
|
+
* ignore-marker line set and skip suppressed matches.
|
|
128
|
+
*/
|
|
129
|
+
export interface ExtractedSpecifier {
|
|
130
|
+
specifier: string;
|
|
131
|
+
line: number;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Returns import specifiers plus 0-indexed line numbers, preserving the
|
|
135
|
+
* same matching behavior as {@link extractImportSpecifiers}.
|
|
136
|
+
*/
|
|
137
|
+
export declare function extractImportSpecifiersWithLines(source: string): ExtractedSpecifier[];
|
|
138
|
+
/**
|
|
139
|
+
* Marker comment operators can use to suppress the forward-import rule
|
|
140
|
+
* for imports that resolve to a basename false positive (two unrelated
|
|
141
|
+
* files with the same leaf name) or for any other situation where the
|
|
142
|
+
* regex-level resolution lands on the wrong patch.
|
|
143
|
+
*
|
|
144
|
+
* Usage: place the comment on the same line as the import, or on the
|
|
145
|
+
* line immediately above it:
|
|
146
|
+
*
|
|
147
|
+
* ```js
|
|
148
|
+
* // fireforge-ignore: forward-import
|
|
149
|
+
* import { Helper } from "resource:///modules/Helper.sys.mjs";
|
|
150
|
+
*
|
|
151
|
+
* import { Helper } from "resource:///modules/Helper.sys.mjs"; // fireforge-ignore: forward-import
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export declare const FORWARD_IMPORT_IGNORE_MARKER = "fireforge-ignore: forward-import";
|
|
155
|
+
/**
|
|
156
|
+
* Returns a Set of 0-indexed line numbers on which the forward-import
|
|
157
|
+
* rule should be suppressed. A marker on line N suppresses matches on
|
|
158
|
+
* line N and N+1 so users can write the marker above the line it
|
|
159
|
+
* describes. Matches on any line past N+1 are not affected.
|
|
160
|
+
*/
|
|
161
|
+
export declare function findForwardImportIgnoreLines(source: string): Set<number>;
|
|
162
|
+
/**
|
|
163
|
+
* Cross-patch lint rule: a patch imports a module that a later patch is
|
|
164
|
+
* responsible for creating.
|
|
165
|
+
*
|
|
166
|
+
* Approach is deliberately conservative — we do not resolve `resource://`
|
|
167
|
+
* URLs to engine file paths. Instead we build a cross-queue index of
|
|
168
|
+
* newly-created files keyed by their basename, and flag imports whose leaf
|
|
169
|
+
* matches an entry owned by a later-ordered patch. False positives from
|
|
170
|
+
* unrelated basename collisions (two different directories happening to
|
|
171
|
+
* create files named `Helper.sys.mjs`) are possible; the README documents
|
|
172
|
+
* the limitation and the inline ignore marker above provides an escape
|
|
173
|
+
* hatch.
|
|
174
|
+
*
|
|
175
|
+
* Rules out:
|
|
176
|
+
* - Imports whose leaf matches a newly-created file in the *same* or an
|
|
177
|
+
* *earlier* patch (legitimate use).
|
|
178
|
+
* - Imports whose leaf is not in the new-file index at all (pre-existing
|
|
179
|
+
* engine file — not our concern).
|
|
180
|
+
* - Imports on a line suppressed by the ignore marker.
|
|
181
|
+
*/
|
|
182
|
+
export declare function lintPatchQueueForwardImports(ctx: PatchQueueContext): PatchLintIssue[];
|
|
183
|
+
/**
|
|
184
|
+
* Cross-patch lint orchestrator. Runs every cross-patch rule against the
|
|
185
|
+
* provided context and returns combined issues.
|
|
186
|
+
*
|
|
187
|
+
* Separate from `lintExportedPatch` because cross-patch rules operate
|
|
188
|
+
* over the whole queue, not a single patch. Callers integrating both
|
|
189
|
+
* orchestrators (e.g. `fireforge lint`) should concatenate results.
|
|
190
|
+
*
|
|
191
|
+
* @param ctx - Pre-built queue context (use
|
|
192
|
+
* {@link buildPatchQueueContext} for the default path, or construct
|
|
193
|
+
* manually for projected/hypothetical states)
|
|
194
|
+
*/
|
|
195
|
+
export declare function lintPatchQueue(ctx: PatchQueueContext): PatchLintIssue[];
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Cross-patch lint infrastructure: the queue context builder, the
|
|
4
|
+
* duplicate-new-file-creation and forward-import rules, the
|
|
5
|
+
* forward-import ignore-marker, and the per-specifier extractor that
|
|
6
|
+
* powers the forward-import rule.
|
|
7
|
+
*
|
|
8
|
+
* Separated from `patch-lint.ts` so the per-patch and cross-patch rule
|
|
9
|
+
* bodies stay within the project's per-file line budget. `patch-lint.ts`
|
|
10
|
+
* re-exports the public surface so callers continue to import from a
|
|
11
|
+
* single module.
|
|
12
|
+
*/
|
|
13
|
+
import { basename } from 'node:path';
|
|
14
|
+
import { toError } from '../utils/errors.js';
|
|
15
|
+
import { readText } from '../utils/fs.js';
|
|
16
|
+
import { verbose } from '../utils/logger.js';
|
|
17
|
+
import { stripJsComments } from '../utils/regex.js';
|
|
18
|
+
import { discoverPatches } from './patch-files.js';
|
|
19
|
+
import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
|
|
20
|
+
import { loadPatchesManifest } from './patch-manifest-io.js';
|
|
21
|
+
import { extractNewFileContent } from './patch-transform.js';
|
|
22
|
+
/**
|
|
23
|
+
* Builds a {@link PatchQueueContext} by reading every .patch file in the
|
|
24
|
+
* directory and extracting new-file content for each creation.
|
|
25
|
+
*
|
|
26
|
+
* Reads manifest metadata best-effort: if the manifest is missing or a patch
|
|
27
|
+
* file has no metadata entry, the context still populates the entry from the
|
|
28
|
+
* filename prefix so cross-patch rules can operate on a drift state. (A
|
|
29
|
+
* separate consistency check — see `patches verify` — is responsible for
|
|
30
|
+
* reporting the drift as its own error.)
|
|
31
|
+
*
|
|
32
|
+
* @param patchesDir - Path to the patches directory
|
|
33
|
+
*/
|
|
34
|
+
export async function buildPatchQueueContext(patchesDir) {
|
|
35
|
+
const patches = await discoverPatches(patchesDir);
|
|
36
|
+
const manifest = await loadPatchesManifest(patchesDir);
|
|
37
|
+
const metadataByFilename = new Map();
|
|
38
|
+
if (manifest) {
|
|
39
|
+
for (const entry of manifest.patches) {
|
|
40
|
+
metadataByFilename.set(entry.filename, entry);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const entries = [];
|
|
44
|
+
for (const patch of patches) {
|
|
45
|
+
const diff = await readText(patch.path);
|
|
46
|
+
const newFilePaths = detectNewFilesInDiff(diff);
|
|
47
|
+
const newFiles = new Map();
|
|
48
|
+
for (const newFile of newFilePaths) {
|
|
49
|
+
try {
|
|
50
|
+
const content = await extractNewFileContent(patch.path, newFile);
|
|
51
|
+
newFiles.set(newFile, content);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
verbose(`Skipping forward-import scan for ${newFile} in ${patch.filename}: ${toError(error).message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Added-line content for every file the patch modifies but does not
|
|
58
|
+
// create. Fed to the forward-import rule so imports introduced into
|
|
59
|
+
// pre-existing files are checked too, not only imports in brand-new
|
|
60
|
+
// files. We deliberately skip paths in newFilePaths — those are
|
|
61
|
+
// already covered by the newFiles map, which carries full content
|
|
62
|
+
// rather than only the added lines.
|
|
63
|
+
const addedLinesByFile = extractAddedLinesPerFile(diff);
|
|
64
|
+
const modifiedFileAdditions = new Map();
|
|
65
|
+
for (const [file, lines] of addedLinesByFile) {
|
|
66
|
+
if (newFilePaths.has(file))
|
|
67
|
+
continue;
|
|
68
|
+
modifiedFileAdditions.set(file, lines.join('\n'));
|
|
69
|
+
}
|
|
70
|
+
entries.push({
|
|
71
|
+
filename: patch.filename,
|
|
72
|
+
order: Number.isFinite(patch.order) ? patch.order : entries.length + 1,
|
|
73
|
+
metadata: metadataByFilename.get(patch.filename) ?? null,
|
|
74
|
+
diff,
|
|
75
|
+
newFiles,
|
|
76
|
+
modifiedFileAdditions,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Sort by order so rules can rely on entries being in apply order.
|
|
80
|
+
entries.sort((a, b) => a.order - b.order || a.filename.localeCompare(b.filename));
|
|
81
|
+
return { entries };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Returns the raw `path → patches[]` map of files created in `new file
|
|
85
|
+
* mode` by at least one patch in the queue. Paths created by only one
|
|
86
|
+
* patch are also included so callers can distinguish "no creator" from
|
|
87
|
+
* "exactly one creator" without re-scanning the diffs.
|
|
88
|
+
*
|
|
89
|
+
* Split out from {@link lintPatchQueueDuplicateCreations} so
|
|
90
|
+
* `status --ownership` (and any future caller that wants ownership
|
|
91
|
+
* without a rendered PatchLintIssue) can consume the same structured
|
|
92
|
+
* data the rule itself relies on. Previously status had to parse the
|
|
93
|
+
* rule's human-readable message to recover the patch list, which was
|
|
94
|
+
* both fragile and made the lint message format part of an implicit
|
|
95
|
+
* contract.
|
|
96
|
+
*
|
|
97
|
+
* @param ctx - Pre-built queue context
|
|
98
|
+
*/
|
|
99
|
+
export function collectNewFileCreatorsByPath(ctx) {
|
|
100
|
+
const creators = new Map();
|
|
101
|
+
for (const entry of ctx.entries) {
|
|
102
|
+
const newFiles = detectNewFilesInDiff(entry.diff);
|
|
103
|
+
for (const file of newFiles) {
|
|
104
|
+
let owners = creators.get(file);
|
|
105
|
+
if (!owners) {
|
|
106
|
+
owners = [];
|
|
107
|
+
creators.set(file, owners);
|
|
108
|
+
}
|
|
109
|
+
owners.push(entry.filename);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return creators;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Cross-patch lint rule: the same path is newly created (`--- /dev/null →
|
|
116
|
+
* +++ b/path`) by more than one patch. This is the failure mode that
|
|
117
|
+
* motivated the rule — Hominis landed three patches each trying to create
|
|
118
|
+
* the same file, and the error surfaced only when import rolled back
|
|
119
|
+
* mid-apply.
|
|
120
|
+
*
|
|
121
|
+
* Reports one error per conflicting path, naming every patch that creates
|
|
122
|
+
* the path so the operator can pick the correct fix (`patch delete` or
|
|
123
|
+
* `re-export --files`).
|
|
124
|
+
*/
|
|
125
|
+
export function lintPatchQueueDuplicateCreations(ctx) {
|
|
126
|
+
const creators = collectNewFileCreatorsByPath(ctx);
|
|
127
|
+
const issues = [];
|
|
128
|
+
for (const [file, owners] of creators) {
|
|
129
|
+
if (owners.length > 1) {
|
|
130
|
+
issues.push({
|
|
131
|
+
file,
|
|
132
|
+
check: 'duplicate-new-file-creation',
|
|
133
|
+
fingerprint: `duplicate-new-file-creation|${file}|${[...owners].sort((a, b) => a.localeCompare(b)).join(',')}`,
|
|
134
|
+
message: `File "${file}" is created (new file mode) by multiple patches: ${owners.join(', ')}. ` +
|
|
135
|
+
'Only one patch may create a given path. Use "patch delete" or ' +
|
|
136
|
+
'"re-export --files" to remove the duplicate.',
|
|
137
|
+
severity: 'error',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return issues;
|
|
142
|
+
}
|
|
143
|
+
const FORWARD_IMPORTABLE_EXTENSIONS = ['.mjs', '.sys.mjs', '.js', '.jsm'];
|
|
144
|
+
/**
|
|
145
|
+
* Returns true when a path looks like a JS module/subscript the
|
|
146
|
+
* forward-import rule should scan.
|
|
147
|
+
*/
|
|
148
|
+
export function isForwardImportableFile(path) {
|
|
149
|
+
return FORWARD_IMPORTABLE_EXTENSIONS.some((ext) => path.endsWith(ext));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Regex-level extractor for module specifiers in ES module / subscript files.
|
|
153
|
+
*
|
|
154
|
+
* Intentionally conservative. Catches:
|
|
155
|
+
* - `import ... from "specifier"` (ES module static imports)
|
|
156
|
+
* - `import "specifier"` (side-effect imports — the `from`
|
|
157
|
+
* clause is optional in the regex)
|
|
158
|
+
* - `import("specifier")` (dynamic imports)
|
|
159
|
+
* - ChromeUtils.defineESModuleGetters(obj, { Name: "specifier", ... })
|
|
160
|
+
*
|
|
161
|
+
* Returns the raw specifier strings — callers should take the leaf basename
|
|
162
|
+
* to match against the newFileIndex, because we do not resolve `resource://`
|
|
163
|
+
* URLs to engine file paths.
|
|
164
|
+
*/
|
|
165
|
+
export function extractImportSpecifiers(source) {
|
|
166
|
+
return extractImportSpecifiersWithLines(source).map((item) => item.specifier);
|
|
167
|
+
}
|
|
168
|
+
function buildLineOffsets(source) {
|
|
169
|
+
const offsets = [0];
|
|
170
|
+
for (let i = 0; i < source.length; i++) {
|
|
171
|
+
if (source[i] === '\n')
|
|
172
|
+
offsets.push(i + 1);
|
|
173
|
+
}
|
|
174
|
+
return offsets;
|
|
175
|
+
}
|
|
176
|
+
function makeOffsetToLine(lineOffsets) {
|
|
177
|
+
return (offset) => {
|
|
178
|
+
let lo = 0;
|
|
179
|
+
let hi = lineOffsets.length - 1;
|
|
180
|
+
while (lo < hi) {
|
|
181
|
+
const mid = (lo + hi + 1) >>> 1;
|
|
182
|
+
const candidate = lineOffsets[mid];
|
|
183
|
+
if (candidate === undefined || candidate > offset) {
|
|
184
|
+
hi = mid - 1;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
lo = mid;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return lo;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Walks `defineESModuleGetters(obj, { ... })` calls using a balanced
|
|
195
|
+
* brace walker so nested object literals and multi-line shapes do not
|
|
196
|
+
* terminate the parse early. Appends the string literals found inside
|
|
197
|
+
* the getter map to `results`.
|
|
198
|
+
*/
|
|
199
|
+
function collectGetterSpecifiers(stripped, results, offsetToLine) {
|
|
200
|
+
const gettersOpenPattern = /defineESModuleGetters\s*\(/g;
|
|
201
|
+
let match;
|
|
202
|
+
while ((match = gettersOpenPattern.exec(stripped)) !== null) {
|
|
203
|
+
const openParen = stripped.indexOf('(', match.index);
|
|
204
|
+
if (openParen === -1)
|
|
205
|
+
continue;
|
|
206
|
+
// Walk to the first top-level `{` inside the call — the start of
|
|
207
|
+
// the getter-map object literal. Bail if we reach the closing `)`
|
|
208
|
+
// first (no object literal argument given).
|
|
209
|
+
let depthParen = 1;
|
|
210
|
+
let openBrace = -1;
|
|
211
|
+
for (let i = openParen + 1; i < stripped.length; i++) {
|
|
212
|
+
const char = stripped[i];
|
|
213
|
+
if (char === '(')
|
|
214
|
+
depthParen += 1;
|
|
215
|
+
else if (char === ')') {
|
|
216
|
+
depthParen -= 1;
|
|
217
|
+
if (depthParen === 0)
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
else if (char === '{' && depthParen === 1) {
|
|
221
|
+
openBrace = i;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (openBrace === -1)
|
|
226
|
+
continue;
|
|
227
|
+
// Walk the object-literal body with balanced braces so nested
|
|
228
|
+
// `{ ... }` inside a value does not terminate the walk early.
|
|
229
|
+
let depthBrace = 1;
|
|
230
|
+
let closeBrace = -1;
|
|
231
|
+
for (let i = openBrace + 1; i < stripped.length; i++) {
|
|
232
|
+
const char = stripped[i];
|
|
233
|
+
if (char === '{')
|
|
234
|
+
depthBrace += 1;
|
|
235
|
+
else if (char === '}') {
|
|
236
|
+
depthBrace -= 1;
|
|
237
|
+
if (depthBrace === 0) {
|
|
238
|
+
closeBrace = i;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (closeBrace === -1)
|
|
244
|
+
continue;
|
|
245
|
+
const body = stripped.slice(openBrace + 1, closeBrace);
|
|
246
|
+
const bodyStart = openBrace + 1;
|
|
247
|
+
const stringLiteralPattern = /["']([^"']+)["']/g;
|
|
248
|
+
let strMatch;
|
|
249
|
+
while ((strMatch = stringLiteralPattern.exec(body)) !== null) {
|
|
250
|
+
if (strMatch[1]) {
|
|
251
|
+
results.push({
|
|
252
|
+
specifier: strMatch[1],
|
|
253
|
+
line: offsetToLine(bodyStart + strMatch.index),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Returns import specifiers plus 0-indexed line numbers, preserving the
|
|
261
|
+
* same matching behavior as {@link extractImportSpecifiers}.
|
|
262
|
+
*/
|
|
263
|
+
export function extractImportSpecifiersWithLines(source) {
|
|
264
|
+
// stripJsComments replaces comment bodies with space runs of equal
|
|
265
|
+
// length, preserving character offsets. That lets us match against
|
|
266
|
+
// the stripped source (so we do not match `import` tokens inside
|
|
267
|
+
// block comments or string literals) while still reporting line
|
|
268
|
+
// numbers based on the ORIGINAL source, which is what the
|
|
269
|
+
// ignore-marker scan walks.
|
|
270
|
+
const stripped = stripJsComments(source);
|
|
271
|
+
const results = [];
|
|
272
|
+
const lineOffsets = buildLineOffsets(source);
|
|
273
|
+
const offsetToLine = makeOffsetToLine(lineOffsets);
|
|
274
|
+
const importFromPattern = /\bimport\s+(?:[^'"]*?\s+from\s+)?["']([^"']+)["']/g;
|
|
275
|
+
let match;
|
|
276
|
+
while ((match = importFromPattern.exec(stripped)) !== null) {
|
|
277
|
+
if (match[1])
|
|
278
|
+
results.push({ specifier: match[1], line: offsetToLine(match.index) });
|
|
279
|
+
}
|
|
280
|
+
const dynamicImportPattern = /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
281
|
+
while ((match = dynamicImportPattern.exec(stripped)) !== null) {
|
|
282
|
+
if (match[1])
|
|
283
|
+
results.push({ specifier: match[1], line: offsetToLine(match.index) });
|
|
284
|
+
}
|
|
285
|
+
collectGetterSpecifiers(stripped, results, offsetToLine);
|
|
286
|
+
return results;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Marker comment operators can use to suppress the forward-import rule
|
|
290
|
+
* for imports that resolve to a basename false positive (two unrelated
|
|
291
|
+
* files with the same leaf name) or for any other situation where the
|
|
292
|
+
* regex-level resolution lands on the wrong patch.
|
|
293
|
+
*
|
|
294
|
+
* Usage: place the comment on the same line as the import, or on the
|
|
295
|
+
* line immediately above it:
|
|
296
|
+
*
|
|
297
|
+
* ```js
|
|
298
|
+
* // fireforge-ignore: forward-import
|
|
299
|
+
* import { Helper } from "resource:///modules/Helper.sys.mjs";
|
|
300
|
+
*
|
|
301
|
+
* import { Helper } from "resource:///modules/Helper.sys.mjs"; // fireforge-ignore: forward-import
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
export const FORWARD_IMPORT_IGNORE_MARKER = 'fireforge-ignore: forward-import';
|
|
305
|
+
/**
|
|
306
|
+
* Returns a Set of 0-indexed line numbers on which the forward-import
|
|
307
|
+
* rule should be suppressed. A marker on line N suppresses matches on
|
|
308
|
+
* line N and N+1 so users can write the marker above the line it
|
|
309
|
+
* describes. Matches on any line past N+1 are not affected.
|
|
310
|
+
*/
|
|
311
|
+
export function findForwardImportIgnoreLines(source) {
|
|
312
|
+
const lines = source.split('\n');
|
|
313
|
+
const ignored = new Set();
|
|
314
|
+
for (let i = 0; i < lines.length; i++) {
|
|
315
|
+
const line = lines[i];
|
|
316
|
+
if (line && line.includes(FORWARD_IMPORT_IGNORE_MARKER)) {
|
|
317
|
+
ignored.add(i);
|
|
318
|
+
ignored.add(i + 1);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return ignored;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Cross-patch lint rule: a patch imports a module that a later patch is
|
|
325
|
+
* responsible for creating.
|
|
326
|
+
*
|
|
327
|
+
* Approach is deliberately conservative — we do not resolve `resource://`
|
|
328
|
+
* URLs to engine file paths. Instead we build a cross-queue index of
|
|
329
|
+
* newly-created files keyed by their basename, and flag imports whose leaf
|
|
330
|
+
* matches an entry owned by a later-ordered patch. False positives from
|
|
331
|
+
* unrelated basename collisions (two different directories happening to
|
|
332
|
+
* create files named `Helper.sys.mjs`) are possible; the README documents
|
|
333
|
+
* the limitation and the inline ignore marker above provides an escape
|
|
334
|
+
* hatch.
|
|
335
|
+
*
|
|
336
|
+
* Rules out:
|
|
337
|
+
* - Imports whose leaf matches a newly-created file in the *same* or an
|
|
338
|
+
* *earlier* patch (legitimate use).
|
|
339
|
+
* - Imports whose leaf is not in the new-file index at all (pre-existing
|
|
340
|
+
* engine file — not our concern).
|
|
341
|
+
* - Imports on a line suppressed by the ignore marker.
|
|
342
|
+
*/
|
|
343
|
+
export function lintPatchQueueForwardImports(ctx) {
|
|
344
|
+
const newFileIndex = new Map();
|
|
345
|
+
for (const entry of ctx.entries) {
|
|
346
|
+
for (const fullPath of entry.newFiles.keys()) {
|
|
347
|
+
const leaf = basename(fullPath);
|
|
348
|
+
let owners = newFileIndex.get(leaf);
|
|
349
|
+
if (!owners) {
|
|
350
|
+
owners = [];
|
|
351
|
+
newFileIndex.set(leaf, owners);
|
|
352
|
+
}
|
|
353
|
+
owners.push({ filename: entry.filename, order: entry.order, fullPath });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const issues = [];
|
|
357
|
+
// Runs the forward-import check against one source site — either a file
|
|
358
|
+
// the patch creates (`content` = full file) or a file the patch modifies
|
|
359
|
+
// (`content` = concatenated added lines only). We deliberately scan added
|
|
360
|
+
// lines rather than the full resulting file for modifications: we only
|
|
361
|
+
// want to flag imports *this patch introduces*, not imports that already
|
|
362
|
+
// exist on HEAD and happen to match a later-created file by coincidence.
|
|
363
|
+
const checkSite = (entry, sitePath, content) => {
|
|
364
|
+
if (!isForwardImportableFile(sitePath))
|
|
365
|
+
return;
|
|
366
|
+
const ignoreLines = findForwardImportIgnoreLines(content);
|
|
367
|
+
const extracted = extractImportSpecifiersWithLines(content);
|
|
368
|
+
for (const { specifier, line } of extracted) {
|
|
369
|
+
if (ignoreLines.has(line))
|
|
370
|
+
continue;
|
|
371
|
+
// Take the leaf and strip query/hash if any.
|
|
372
|
+
const cleaned = specifier.split(/[?#]/)[0] ?? specifier;
|
|
373
|
+
const leaf = basename(cleaned);
|
|
374
|
+
if (!leaf || !isForwardImportableFile(leaf))
|
|
375
|
+
continue;
|
|
376
|
+
const owners = newFileIndex.get(leaf);
|
|
377
|
+
if (!owners)
|
|
378
|
+
continue;
|
|
379
|
+
// Is the owner a later-ordered patch (or one ordered equal but
|
|
380
|
+
// lexicographically later as a tiebreaker)?
|
|
381
|
+
const laterOwners = owners.filter((owner) => owner.order > entry.order ||
|
|
382
|
+
(owner.order === entry.order && owner.filename > entry.filename));
|
|
383
|
+
if (laterOwners.length === 0)
|
|
384
|
+
continue;
|
|
385
|
+
const ownersSummary = laterOwners
|
|
386
|
+
.map((o) => `${o.filename} (creates ${o.fullPath})`)
|
|
387
|
+
.join(', ');
|
|
388
|
+
const fingerprintOwners = [...laterOwners]
|
|
389
|
+
.map((o) => `${o.filename}:${o.fullPath}`)
|
|
390
|
+
.sort((a, b) => a.localeCompare(b))
|
|
391
|
+
.join(',');
|
|
392
|
+
issues.push({
|
|
393
|
+
file: sitePath,
|
|
394
|
+
check: 'forward-import',
|
|
395
|
+
fingerprint: `forward-import|${sitePath}|${cleaned}|${fingerprintOwners}`,
|
|
396
|
+
message: `${sitePath} in ${entry.filename} imports "${specifier}", ` +
|
|
397
|
+
`but the matching new file is created by a later patch: ${ownersSummary}. ` +
|
|
398
|
+
'Reorder the patches so the dependency is created first, move the import ' +
|
|
399
|
+
'into the later patch, or mark the import with ' +
|
|
400
|
+
`"// ${FORWARD_IMPORT_IGNORE_MARKER}" if the basename collision is a false positive.`,
|
|
401
|
+
severity: 'error',
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
for (const entry of ctx.entries) {
|
|
406
|
+
for (const [path, content] of entry.newFiles)
|
|
407
|
+
checkSite(entry, path, content);
|
|
408
|
+
for (const [path, added] of entry.modifiedFileAdditions)
|
|
409
|
+
checkSite(entry, path, added);
|
|
410
|
+
}
|
|
411
|
+
return issues;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Cross-patch lint orchestrator. Runs every cross-patch rule against the
|
|
415
|
+
* provided context and returns combined issues.
|
|
416
|
+
*
|
|
417
|
+
* Separate from `lintExportedPatch` because cross-patch rules operate
|
|
418
|
+
* over the whole queue, not a single patch. Callers integrating both
|
|
419
|
+
* orchestrators (e.g. `fireforge lint`) should concatenate results.
|
|
420
|
+
*
|
|
421
|
+
* @param ctx - Pre-built queue context (use
|
|
422
|
+
* {@link buildPatchQueueContext} for the default path, or construct
|
|
423
|
+
* manually for projected/hypothetical states)
|
|
424
|
+
*/
|
|
425
|
+
export function lintPatchQueue(ctx) {
|
|
426
|
+
return [...lintPatchQueueDuplicateCreations(ctx), ...lintPatchQueueForwardImports(ctx)];
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=patch-lint-cross.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified-diff walking helpers shared between per-patch lint rules,
|
|
3
|
+
* cross-patch lint rules, and the export / re-export projection paths.
|
|
4
|
+
*
|
|
5
|
+
* Factored out of `patch-lint.ts` so the per-patch lint body and
|
|
6
|
+
* cross-patch lint body (in `patch-lint-cross.ts`) can both depend on
|
|
7
|
+
* the same diff walkers without inducing a circular import. Callers
|
|
8
|
+
* should keep importing these through `patch-lint.ts` — this file is
|
|
9
|
+
* an implementation detail.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Extracts new-file paths from a unified diff by scanning for `new file mode` markers.
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectNewFilesInDiff(diffContent: string): Set<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Extracts added lines per file from a unified diff.
|
|
17
|
+
* Returns a map of file path → array of added line contents (without the leading `+`).
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractAddedLinesPerFile(diffContent: string): Map<string, string[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Builds the `modifiedFileAdditions` map the cross-patch lint expects for
|
|
22
|
+
* a given unified diff. Exposed so callers that construct synthetic /
|
|
23
|
+
* projected `PatchQueueEntry` values (notably `re-export --files`
|
|
24
|
+
* and `export --order`) can populate the field identically to
|
|
25
|
+
* `buildPatchQueueContext`.
|
|
26
|
+
*
|
|
27
|
+
* Matches buildPatchQueueContext's algorithm exactly: skip paths that are
|
|
28
|
+
* created by the diff — those are already covered by the `newFiles` map,
|
|
29
|
+
* which carries full content rather than only the added lines.
|
|
30
|
+
*
|
|
31
|
+
* @param diff - Unified diff content
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildModifiedFileAdditionsFromDiff(diff: string): Map<string, string>;
|