@hominis/fireforge 0.18.11 → 0.19.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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.0
4
+
5
+ ### Features
6
+
7
+ - **`patchLint.checkJsStrict` and `patchLint.checkJsCompilerOptions`.** The patch-lint `checkJs` pass now defaults to the historical loose preset (`strict: false`, `noImplicitAny: false`). Set `"patchLint": { "checkJs": true, "checkJsStrict": true }` to enforce `strict` and `noImplicitAny` on patch-owned `.sys.mjs` so implicit-any parameters surface as `checkjs-type-error`, aligning with strict whole-project checkJs without changing module resolution (`noResolve` and `resource://` suppression unchanged). Optional `checkJsCompilerOptions` (requires `checkJsStrict`) merges allowlisted boolean compiler overrides — for example `{ "strictNullChecks": false }` — after the strict preset for gradual adoption. The Firefox globals shim and `SUPPRESSED_DIAGNOSTIC_CODES` remain shared with `fireforge typecheck` via `typecheck-shim.ts`.
8
+
9
+ ### Hardening
10
+
11
+ - **`modified-file-missing-header` — standard Mozilla MPL-2.0 block headers with wrapped line breaks.** The upstream fallback scan required a contiguous `Mozilla Public License` substring in the first few lines, so files that follow Mozilla’s usual `/* … Mozilla Public` / ` * License, v. 2.0 … */` wrap (including after Emacs/vim directive blocks) were warned despite a valid notice. `containsUpstreamLicenseText` in `src/core/license-headers.ts` now normalizes common block-comment continuation prefixes before matching, so forks need not add SPDX solely to satisfy patch lint.
12
+
3
13
  ## 0.18.0
4
14
 
5
15
  ### Compatibility
package/README.md CHANGED
@@ -230,34 +230,34 @@ If the manifest drifts after an interrupted export or manual edits, `fireforge i
230
230
 
231
231
  By default, a standalone `fireforge lint` (no arguments) lints the **aggregate** `git diff HEAD` — i.e. every applied patch summed — with tool-managed branding paths (`browser/branding/<binaryName>/`) excluded. A fresh-setup workspace carries a large generated branding diff that operators did not author directly, and letting it through tripped the patch-size and license-header rules on content that matches the `branding` bucket in `fireforge status`. When the exclusion fires the command prints a one-line note naming the excluded count so the filter is visible. On a repo where `fireforge import` or `fireforge rebase` has just applied the full queue, the patch-size rules (`large-patch-lines`, `large-patch-files`) fire against the sum, which reads as "my queue is broken" when it is really an artefact of aggregation. Use `fireforge lint --per-patch` to rescope the diff to each patch's own `filesAffected`, honouring the patch's own `lintIgnore`. Cross-patch rules (`duplicate-new-file-creation`, `forward-import`) still run once over the whole queue either way. Pass explicit file paths to narrow the scope further — explicit-path mode does lint branding files (the operator's explicit request wins over the branding exclusion); the three modes (aggregate, file-scoped, per-patch) are mutually exclusive.
232
232
 
233
- | Check | Scope | Severity |
234
- | ------------------------------------ | ------------------------------------------------------------------------------------------------ | ------------------------ |
235
- | `missing-license-header` | New files (JS/CSS/FTL) | error |
236
- | `relative-import` | JS/MJS files | error |
237
- | `token-prefix-violation` | CSS files (with furnace) | error |
238
- | `raw-color-value` | Introduced CSS color values (allowlist via `patchLint.rawColorAllowlist`) | error |
239
- | `duplicate-new-file-creation` | Same path created by multiple patches | error |
240
- | `forward-import` | Patch imports from a later-patch file | error |
241
- | `missing-jsdoc` | Exports in patch-owned `.sys.mjs` | error |
242
- | `jsdoc-param-mismatch` | Exports in patch-owned `.sys.mjs` | error |
243
- | `jsdoc-missing-returns` | Exports in patch-owned `.sys.mjs` | error |
244
- | `checkjs-type-error` | Patch-owned `.sys.mjs` (opt-in) | error |
245
- | `missing-jsdoc-class-method` | Class-method exports in patch-owned `.sys.mjs` (opt-in) | configurable |
246
- | `jsdoc-class-method-param-mismatch` | Class-method exports in patch-owned `.sys.mjs` (opt-in) | configurable |
247
- | `jsdoc-class-method-missing-returns` | Class-method exports in patch-owned `.sys.mjs` (opt-in) | configurable |
248
- | `test-needs-assertion` | Patch-introduced `browser_*.js` test files (opt-in) | configurable |
249
- | `missing-modification-comment` | Modified upstream JS/MJS | warning |
250
- | `modified-file-missing-header` | Modified upstream files (JS/CSS/FTL) | warning |
251
- | `file-too-large` | New files (tiered: 500/750/900 general, 1200/1400/1600 test) | notice / warning / error |
252
- | `observer-topic-naming` | Observer topics with binaryName | warning |
253
- | `large-patch-files` | Patches affecting many files (tiered: >5 general, >5 test, >60 branding) | warning |
254
- | `large-patch-lines` | Patch line count (tiered: 800/1500/3000 general, 1500/3000/6000 test, 8000/18000/30000 branding) | notice / warning / error |
233
+ | Check | Scope | Severity |
234
+ | ------------------------------------ | ------------------------------------------------------------------------------------------------------- | ------------------------ |
235
+ | `missing-license-header` | New files (JS/CSS/FTL) | error |
236
+ | `relative-import` | JS/MJS files | error |
237
+ | `token-prefix-violation` | CSS files (with furnace) | error |
238
+ | `raw-color-value` | Introduced CSS color values (allowlist via `patchLint.rawColorAllowlist`) | error |
239
+ | `duplicate-new-file-creation` | Same path created by multiple patches | error |
240
+ | `forward-import` | Patch imports from a later-patch file | error |
241
+ | `missing-jsdoc` | Exports in patch-owned `.sys.mjs` | error |
242
+ | `jsdoc-param-mismatch` | Exports in patch-owned `.sys.mjs` | error |
243
+ | `jsdoc-missing-returns` | Exports in patch-owned `.sys.mjs` | error |
244
+ | `checkjs-type-error` | Patch-owned `.sys.mjs` (opt-in `patchLint.checkJs`) | error |
245
+ | `missing-jsdoc-class-method` | Class-method exports in patch-owned `.sys.mjs` (opt-in) | configurable |
246
+ | `jsdoc-class-method-param-mismatch` | Class-method exports in patch-owned `.sys.mjs` (opt-in) | configurable |
247
+ | `jsdoc-class-method-missing-returns` | Class-method exports in patch-owned `.sys.mjs` (opt-in) | configurable |
248
+ | `test-needs-assertion` | Patch-introduced `browser_*.js` test files (opt-in) | configurable |
249
+ | `missing-modification-comment` | Modified upstream JS/MJS | warning |
250
+ | `modified-file-missing-header` | Modified upstream files (JS/CSS/FTL); Mozilla MPL-2.0 `/* */` headers with wrapped lines are recognized | warning |
251
+ | `file-too-large` | New files (tiered: 500/750/900 general, 1200/1400/1600 test) | notice / warning / error |
252
+ | `observer-topic-naming` | Observer topics with binaryName | warning |
253
+ | `large-patch-files` | Patches affecting many files (tiered: >5 general, >5 test, >60 branding) | warning |
254
+ | `large-patch-lines` | Patch line count (tiered: 800/1500/3000 general, 1500/3000/6000 test, 8000/18000/30000 branding) | notice / warning / error |
255
255
 
256
256
  **JSDoc validation** uses AST-based analysis (Acorn) to validate exported APIs in patch-owned `.sys.mjs` files. A file is "patch-owned" if it was newly created by the current diff or by an existing patch in the queue. Functions must document every `@param` (names must match) and include `@returns` when the function returns a value. Exported constants and classes require a JSDoc block.
257
257
 
258
- **Optional `checkJs` pass.** Enable a TypeScript-esque bastardization of type checking for patch-owned `.sys.mjs` files by adding `"patchLint": { "checkJs": true }` to `fireforge.json`. This uses the TypeScript compiler API with `allowJs + checkJs + noEmit`, scoped only to patch-owned files. Firefox globals (`Services`, `ChromeUtils`, `lazy`, etc.) are shimmed automatically. Module-resolution errors from Firefox's `resource://` and `chrome://` URL schemes are suppressed since TypeScript cannot follow these. This pass solely focuses on type errors within the patch-owned code itself (mismatched JSDoc types, wrong argument counts, unreachable code, etc.). Projects that need to extend the built-in shim (e.g. for `MozLitElement`, `MozXULElement`, or fork-specific component bases) can point at an additional `.d.ts` via `"patchLint": { "checkJsExtraShim": "tools/types/<fork>-globals.d.ts" }`; the file is concatenated to the built-in shim — augment, don't redeclare.
258
+ **Optional `checkJs` pass.** Enable a TypeScript-esque bastardization of type checking for patch-owned `.sys.mjs` files by adding `"patchLint": { "checkJs": true }` to `fireforge.json`. This uses the TypeScript compiler API with `allowJs + checkJs + noEmit`, scoped only to patch-owned files. Firefox globals (`Services`, `ChromeUtils`, `lazy`, etc.) are shimmed automatically. Module-resolution errors from Firefox's `resource://` and `chrome://` URL schemes are suppressed since TypeScript cannot follow these. By default the pass uses a **loose** compiler preset (`strict: false`, `noImplicitAny: false`) so implicit `any` from untyped parameters does not flood the output. Set `"patchLint": { "checkJsStrict": true }` (requires `checkJs: true`) to enable `strict` and `noImplicitAny` for parity with strict whole-project checkJs implicit-any parameters then surface as `checkjs-type-error`. Optional `"patchLint": { "checkJsCompilerOptions": { "strictNullChecks": false } }` (requires `checkJsStrict: true`) merges **allowlisted boolean** strict flags after that preset so forks can tighten `noImplicitAny` while relaxing e.g. null checks. The same built-in shim and the same eight suppressed diagnostic codes as `fireforge typecheck` apply; only explicit strictness differs. Projects that need to extend the built-in shim (e.g. for `MozLitElement`, `MozXULElement`, or fork-specific component bases) can point at an additional `.d.ts` via `"patchLint": { "checkJsExtraShim": "tools/types/<fork>-globals.d.ts" }`; the file is concatenated to the built-in shim — augment, don't redeclare.
259
259
 
260
- **Whole-project type checking — `fireforge typecheck`.** `patchLint.checkJs` is patch-hygiene: scoped to patch-owned `.sys.mjs`, suppresses module-resolution noise, and runs every time `fireforge lint` runs. `fireforge typecheck` is the CI-grade complement: it runs whole projects you point at via `typecheck.projects` in `fireforge.json`, honours each jsconfig's strictness/include/exclude/`paths`, and is intended as a CI gate. The two are complementary; the recommended setup is `fireforge lint` on every patch export and `fireforge typecheck` on CI for the project-level baseline.
260
+ **Whole-project type checking — `fireforge typecheck`.** `patchLint.checkJs` is patch-hygiene: scoped to patch-owned `.sys.mjs`, suppresses module-resolution noise, and runs every time `fireforge lint` runs. With `checkJsStrict`, that pass can match a strict jsconfig's `noImplicitAny` behaviour without replacing full-project resolution. `fireforge typecheck` remains the CI-grade complement: it runs whole projects you point at via `typecheck.projects` in `fireforge.json`, honours each jsconfig's strictness/include/exclude/`paths`, and is intended as a CI gate. The two are complementary; the recommended setup is `fireforge lint` on every patch export and `fireforge typecheck` on CI for the project-level baseline.
261
261
 
262
262
  ```jsonc
263
263
  {
@@ -19,7 +19,7 @@ export declare const SRC_DIR = "src";
19
19
  /** Supported top-level fireforge.json keys backed by the current schema. */
20
20
  export declare const SUPPORTED_CONFIG_ROOT_KEYS: readonly ["name", "vendor", "appId", "binaryName", "firefox", "build", "license", "wire", "patchLint", "typecheck", "markerComment"];
21
21
  /** Supported config paths that can be read or set without --force. */
22
- export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "build", "build.jobs", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsExtraShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
22
+ export declare const SUPPORTED_CONFIG_PATHS: readonly ["name", "vendor", "appId", "binaryName", "license", "firefox", "firefox.version", "firefox.product", "build", "build.jobs", "wire", "wire.subscriptDir", "patchLint", "patchLint.checkJs", "patchLint.checkJsStrict", "patchLint.checkJsCompilerOptions", "patchLint.checkJsExtraShim", "patchLint.rawColorAllowlist", "patchLint.jsdocClassMethods", "patchLint.testAssertionFloor", "patchLint.chromeScriptJsDoc", "typecheck", "typecheck.projects", "typecheck.extraShim", "markerComment"];
23
23
  /**
24
24
  * Gets all project paths based on a root directory.
25
25
  * @param root - Root directory of the project
@@ -47,6 +47,8 @@ export const SUPPORTED_CONFIG_PATHS = [
47
47
  'wire.subscriptDir',
48
48
  'patchLint',
49
49
  'patchLint.checkJs',
50
+ 'patchLint.checkJsStrict',
51
+ 'patchLint.checkJsCompilerOptions',
50
52
  'patchLint.checkJsExtraShim',
51
53
  'patchLint.rawColorAllowlist',
52
54
  'patchLint.jsdocClassMethods',
@@ -201,6 +201,36 @@ function optionalConfigObject(rec, key) {
201
201
  }
202
202
  }
203
203
  const SEVERITY_GATE_VALUES = ['off', 'warning', 'error'];
204
+ /** Allowlisted keys for `patchLint.checkJsCompilerOptions` (boolean overrides only). */
205
+ const PATCH_LINT_CHECKJS_COMPILER_OPTION_KEYS = [
206
+ 'strictNullChecks',
207
+ 'strictFunctionTypes',
208
+ 'strictBindCallApply',
209
+ 'noImplicitThis',
210
+ 'useUnknownInCatchVariables',
211
+ 'strictPropertyInitialization',
212
+ 'noUnusedLocals',
213
+ 'noUnusedParameters',
214
+ ];
215
+ function parsePatchLintCheckJsCompilerOptions(raw) {
216
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
217
+ throw new ConfigError('Config field "patchLint.checkJsCompilerOptions" must be a plain object');
218
+ }
219
+ const rec = raw;
220
+ const allowed = new Set(PATCH_LINT_CHECKJS_COMPILER_OPTION_KEYS);
221
+ const out = {};
222
+ for (const key of Object.keys(rec)) {
223
+ if (!allowed.has(key)) {
224
+ throw new ConfigError(`Config field "patchLint.checkJsCompilerOptions" has unknown key "${key}"`);
225
+ }
226
+ const val = rec[key];
227
+ if (typeof val !== 'boolean') {
228
+ throw new ConfigError(`Config field "patchLint.checkJsCompilerOptions.${key}" must be a boolean`);
229
+ }
230
+ out[key] = val;
231
+ }
232
+ return out;
233
+ }
204
234
  function parseSeverityGate(raw, label) {
205
235
  if (raw === undefined)
206
236
  return undefined;
@@ -218,6 +248,17 @@ function parsePatchLintBlock(rec) {
218
248
  }
219
249
  out.checkJs = checkJs;
220
250
  }
251
+ const checkJsStrict = rec.raw('checkJsStrict');
252
+ if (checkJsStrict !== undefined) {
253
+ if (typeof checkJsStrict !== 'boolean') {
254
+ throw new ConfigError('Config field "patchLint.checkJsStrict" must be a boolean');
255
+ }
256
+ out.checkJsStrict = checkJsStrict;
257
+ }
258
+ const checkJsCompilerOptionsRaw = rec.raw('checkJsCompilerOptions');
259
+ if (checkJsCompilerOptionsRaw !== undefined) {
260
+ out.checkJsCompilerOptions = parsePatchLintCheckJsCompilerOptions(checkJsCompilerOptionsRaw);
261
+ }
221
262
  const checkJsExtraShim = rec.raw('checkJsExtraShim');
222
263
  if (checkJsExtraShim !== undefined) {
223
264
  out.checkJsExtraShim = parseShimPath(checkJsExtraShim, 'patchLint.checkJsExtraShim');
@@ -242,6 +283,12 @@ function parsePatchLintBlock(rec) {
242
283
  if (chromeScriptJsDoc !== undefined) {
243
284
  out.chromeScriptJsDoc = chromeScriptJsDoc;
244
285
  }
286
+ if (out.checkJsStrict === true && out.checkJs !== true) {
287
+ throw new ConfigError('Config field "patchLint.checkJsStrict" requires "patchLint.checkJs": true');
288
+ }
289
+ if (out.checkJsCompilerOptions !== undefined && out.checkJsStrict !== true) {
290
+ throw new ConfigError('Config field "patchLint.checkJsCompilerOptions" requires "patchLint.checkJsStrict": true');
291
+ }
245
292
  return out;
246
293
  }
247
294
  /**
@@ -129,6 +129,16 @@ export function hasAnyLicenseHeaderAnyStyle(content) {
129
129
  const styles = ['js', 'css', 'hash'];
130
130
  return styles.some((style) => hasAnyLicenseHeader(content, style));
131
131
  }
132
+ /**
133
+ * Collapses block-comment continuation lines (`\n` + ` * ` prefixes) so
134
+ * substring markers like `Mozilla Public License` match Mozilla's wrapped
135
+ * MPL boilerplate.
136
+ */
137
+ function normalizeLicenseHeadForScan(head) {
138
+ let s = head.replace(/\r\n?/g, '\n');
139
+ s = s.replace(/\n[ \t]*\*[ \t]*/g, ' ');
140
+ return s.replace(/\s+/g, ' ').trim();
141
+ }
132
142
  /**
133
143
  * Returns true if the first few lines of `content` contain a recognized
134
144
  * upstream license identifier string.
@@ -138,6 +148,7 @@ export function hasAnyLicenseHeaderAnyStyle(content) {
138
148
  */
139
149
  export function containsUpstreamLicenseText(content, maxLines = 10) {
140
150
  const head = content.split('\n').slice(0, maxLines).join('\n');
151
+ const scanText = normalizeLicenseHeadForScan(head);
141
152
  const markers = [
142
153
  'Mozilla Public License',
143
154
  'SPDX-License-Identifier',
@@ -145,7 +156,7 @@ export function containsUpstreamLicenseText(content, maxLines = 10) {
145
156
  'MIT License',
146
157
  'GNU General Public License',
147
158
  ];
148
- return markers.some((marker) => head.includes(marker));
159
+ return markers.some((marker) => scanText.includes(marker));
149
160
  }
150
161
  /**
151
162
  * Prepends the license header to a file on disk if it is not already present.
@@ -12,8 +12,12 @@
12
12
  * diagnostic code list now live in `typecheck-shim.ts` and are shared
13
13
  * with the whole-project `fireforge typecheck` command — keeping a
14
14
  * single source of truth for the Firefox-globals coverage.
15
+ * `patchLint.checkJsStrict` only tightens `strict` / `noImplicitAny`
16
+ * and optional allowlisted `checkJsCompilerOptions`; it does not change
17
+ * shim composition or suppressed diagnostic codes.
15
18
  */
16
19
  import type { PatchLintIssue } from '../types/commands/index.js';
20
+ import type { PatchLintCheckJsCompilerOptions, PatchLintConfig } from '../types/config.js';
17
21
  /**
18
22
  * Runs TypeScript's checkJs pass on patch-owned `.sys.mjs` files.
19
23
  *
@@ -28,6 +32,18 @@ import type { PatchLintIssue } from '../types/commands/index.js';
28
32
  * @param projectRoot - Absolute project root for resolving `extraShimPath`.
29
33
  * Defaults to `repoDir` for back-compat with callers that don't
30
34
  * pass an extra shim (no resolution actually happens in that case).
35
+ * @param mode - When `strict` is true, enables `strict` and `noImplicitAny`
36
+ * (CI-style). Optional `compilerOptions` merges allowlisted boolean
37
+ * overrides after that preset (from `patchLint.checkJsCompilerOptions`).
38
+ * Omitted or `{ strict: false }` preserves the historical loose preset.
31
39
  * @returns Array of lint issues from TS diagnostics
32
40
  */
33
- export declare function runCheckJs(repoDir: string, patchOwnedFiles: Set<string>, extraShimPath?: string, projectRoot?: string): Promise<PatchLintIssue[]>;
41
+ export declare function runCheckJs(repoDir: string, patchOwnedFiles: Set<string>, extraShimPath?: string, projectRoot?: string, mode?: {
42
+ strict: boolean;
43
+ compilerOptions?: PatchLintCheckJsCompilerOptions;
44
+ }): Promise<PatchLintIssue[]>;
45
+ /**
46
+ * Invokes {@link runCheckJs} for a `patchLint` block with `checkJs: true`.
47
+ * `projectRoot` is the FireForge project root (`dirname(engine)`).
48
+ */
49
+ export declare function invokePatchLintCheckJs(repoDir: string, patchOwnedFiles: Set<string>, patchLint: PatchLintConfig, projectRoot: string): Promise<PatchLintIssue[]>;
@@ -13,6 +13,9 @@
13
13
  * diagnostic code list now live in `typecheck-shim.ts` and are shared
14
14
  * with the whole-project `fireforge typecheck` command — keeping a
15
15
  * single source of truth for the Firefox-globals coverage.
16
+ * `patchLint.checkJsStrict` only tightens `strict` / `noImplicitAny`
17
+ * and optional allowlisted `checkJsCompilerOptions`; it does not change
18
+ * shim composition or suppressed diagnostic codes.
16
19
  */
17
20
  import { resolve } from 'node:path';
18
21
  import { pathExists } from '../utils/fs.js';
@@ -35,9 +38,13 @@ import { composeShimSource, SHIM_FILENAME, SUPPRESSED_DIAGNOSTIC_CODES } from '.
35
38
  * @param projectRoot - Absolute project root for resolving `extraShimPath`.
36
39
  * Defaults to `repoDir` for back-compat with callers that don't
37
40
  * pass an extra shim (no resolution actually happens in that case).
41
+ * @param mode - When `strict` is true, enables `strict` and `noImplicitAny`
42
+ * (CI-style). Optional `compilerOptions` merges allowlisted boolean
43
+ * overrides after that preset (from `patchLint.checkJsCompilerOptions`).
44
+ * Omitted or `{ strict: false }` preserves the historical loose preset.
38
45
  * @returns Array of lint issues from TS diagnostics
39
46
  */
40
- export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projectRoot) {
47
+ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projectRoot, mode) {
41
48
  if (patchOwnedFiles.size === 0)
42
49
  return [];
43
50
  // Dynamic import — typescript stays as a dev dependency
@@ -93,11 +100,28 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
93
100
  }
94
101
  const shimPath = resolve(repoDir, SHIM_FILENAME);
95
102
  rootFiles.push(shimPath);
103
+ const strict = mode?.strict === true;
104
+ const strictness = strict
105
+ ? { strict: true, noImplicitAny: true }
106
+ : {
107
+ // Loose default — implicit `any` is allowed unless `patchLint.checkJsStrict`.
108
+ strict: false,
109
+ noImplicitAny: false,
110
+ };
111
+ const overrides = {};
112
+ const co = mode?.compilerOptions;
113
+ if (co) {
114
+ for (const key of Object.keys(co)) {
115
+ const v = co[key];
116
+ if (v !== undefined) {
117
+ overrides[key] = v;
118
+ }
119
+ }
120
+ }
96
121
  const options = {
97
122
  allowJs: true,
98
123
  checkJs: true,
99
124
  noEmit: true,
100
- strict: false,
101
125
  target: ts.ScriptTarget.ESNext,
102
126
  module: ts.ModuleKind.ESNext,
103
127
  moduleResolution: ts.ModuleResolutionKind.Bundler,
@@ -108,10 +132,8 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
108
132
  // resource:// and chrome:// import, flooding the output with
109
133
  // "Cannot find module" errors for upstream Firefox modules.
110
134
  noResolve: true,
111
- // Suppress implicit-any noise — Firefox code rarely has full type
112
- // annotations and drowning users in thousands of implicit-any
113
- // errors defeats the purpose of a focused check.
114
- noImplicitAny: false,
135
+ ...strictness,
136
+ ...overrides,
115
137
  };
116
138
  // Custom compiler host: reads patch-owned files from disk, returns
117
139
  // the shim for the shim path, and returns empty content for
@@ -189,4 +211,15 @@ export async function runCheckJs(repoDir, patchOwnedFiles, extraShimPath, projec
189
211
  verbose(`checkJs: analyzed ${rootFiles.length - 1} file(s), found ${issues.length} issue(s)`);
190
212
  return issues;
191
213
  }
214
+ /**
215
+ * Invokes {@link runCheckJs} for a `patchLint` block with `checkJs: true`.
216
+ * `projectRoot` is the FireForge project root (`dirname(engine)`).
217
+ */
218
+ export async function invokePatchLintCheckJs(repoDir, patchOwnedFiles, patchLint, projectRoot) {
219
+ const strict = patchLint.checkJsStrict === true;
220
+ const mode = strict && patchLint.checkJsCompilerOptions
221
+ ? { strict, compilerOptions: patchLint.checkJsCompilerOptions }
222
+ : { strict };
223
+ return runCheckJs(repoDir, patchOwnedFiles, patchLint.checkJsExtraShim, projectRoot, mode);
224
+ }
192
225
  //# sourceMappingURL=patch-lint-checkjs.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Public re-exports for {@link ./patch-lint.ts}. Split out so the
3
+ * orchestrator stays within the ESLint `max-lines` budget.
4
+ */
5
+ export { runCheckJs } from './patch-lint-checkjs.js';
6
+ export { buildPatchQueueContext, collectNewFileCreatorsByPath, type ExtractedSpecifier, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, type PatchQueueContext, type PatchQueueEntry, } from './patch-lint-cross.js';
7
+ export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
8
+ export { type JsDocCheck, type JsDocIssue, validateExportJsDoc } from './patch-lint-jsdoc.js';
9
+ export { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
@@ -0,0 +1,11 @@
1
+ // SPDX-License-Identifier: EUPL-1.2
2
+ /**
3
+ * Public re-exports for {@link ./patch-lint.ts}. Split out so the
4
+ * orchestrator stays within the ESLint `max-lines` budget.
5
+ */
6
+ export { runCheckJs } from './patch-lint-checkjs.js';
7
+ export { buildPatchQueueContext, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, } from './patch-lint-cross.js';
8
+ export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
9
+ export { validateExportJsDoc } from './patch-lint-jsdoc.js';
10
+ export { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
11
+ //# sourceMappingURL=patch-lint-reexports.js.map
@@ -1,11 +1,7 @@
1
1
  import type { PatchLintIssue } from '../types/commands/index.js';
2
2
  import type { FireForgeConfig } from '../types/config.js';
3
3
  import { type CommentStyle } from './license-headers.js';
4
- export { runCheckJs } from './patch-lint-checkjs.js';
5
- export { buildPatchQueueContext, collectNewFileCreatorsByPath, type ExtractedSpecifier, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, type PatchQueueContext, type PatchQueueEntry, } from './patch-lint-cross.js';
6
- export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
7
- export { type JsDocCheck, type JsDocIssue, validateExportJsDoc } from './patch-lint-jsdoc.js';
8
- export { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
4
+ export * from './patch-lint-reexports.js';
9
5
  /**
10
6
  * Counts the total lines in a unified diff and the number of non-binary
11
7
  * text lines, so binary hunks do not inflate patch size checks.
@@ -6,7 +6,7 @@ import { verbose } from '../utils/logger.js';
6
6
  import { hasRawCssColors, stripJsComments } from '../utils/regex.js';
7
7
  import { loadFurnaceConfig } from './furnace-config.js';
8
8
  import { containsUpstreamLicenseText, getLicenseHeader, hasAnyLicenseHeader, hasAnyLicenseHeaderAnyStyle, } from './license-headers.js';
9
- import { runCheckJs } from './patch-lint-checkjs.js';
9
+ import { invokePatchLintCheckJs } from './patch-lint-checkjs.js';
10
10
  import { lintChromeScriptJsDocForFile } from './patch-lint-chrome-jsdoc.js';
11
11
  import { detectNewFilesInDiff, extractAddedLinesPerFile } from './patch-lint-diff.js';
12
12
  import { AGGREGATE_PATCH_FILE } from './patch-lint-diff-tag.js';
@@ -20,12 +20,9 @@ import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch
20
20
  // creation and forward-import rules, ignore marker) lives in
21
21
  // `patch-lint-cross.ts` so the per-patch and cross-patch rule bodies can
22
22
  // each stay within the project's per-file line budget. Re-export the
23
- // public surface so callers continue to import from a single module.
24
- export { runCheckJs } from './patch-lint-checkjs.js';
25
- export { buildPatchQueueContext, collectNewFileCreatorsByPath, extractImportSpecifiers, extractImportSpecifiersWithLines, findForwardImportIgnoreLines, FORWARD_IMPORT_IGNORE_MARKER, isForwardImportableFile, lintPatchQueue, lintPatchQueueDuplicateCreations, lintPatchQueueForwardImports, } from './patch-lint-cross.js';
26
- export { buildModifiedFileAdditionsFromDiff, detectNewFilesInDiff } from './patch-lint-diff.js';
27
- export { validateExportJsDoc } from './patch-lint-jsdoc.js';
28
- export { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs } from './patch-lint-ownership.js';
23
+ // public surface from `patch-lint-reexports.ts` so callers continue to
24
+ // import from a single module.
25
+ export * from './patch-lint-reexports.js';
29
26
  // ---------------------------------------------------------------------------
30
27
  // Helpers
31
28
  // ---------------------------------------------------------------------------
@@ -737,12 +734,8 @@ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, con
737
734
  ...jsIssues,
738
735
  ...modCommentIssues,
739
736
  ];
740
- // Optional checkJs pass — only when explicitly enabled in config.
741
- // `checkJsExtraShim` is project-relative; resolve against the
742
- // project root (dirname(engine) by getProjectPaths convention).
743
737
  if (config.patchLint?.checkJs) {
744
- const extraShim = config.patchLint.checkJsExtraShim;
745
- issues.push(...(await runCheckJs(repoDir, patchOwnedFiles, extraShim, dirname(repoDir))));
738
+ issues.push(...(await invokePatchLintCheckJs(repoDir, patchOwnedFiles, config.patchLint, dirname(repoDir))));
746
739
  }
747
740
  // Filter out ignored checks last so every rule still runs (keeps the
748
741
  // implementation uniform) but suppressed rules do not surface. We do not
@@ -89,12 +89,40 @@ export interface WireConfig {
89
89
  * `'warning'` and `'error'` emit issues at the matching severity.
90
90
  */
91
91
  export type PatchLintSeverityGate = 'off' | 'warning' | 'error';
92
+ /**
93
+ * Allowlisted TypeScript `compilerOptions` overrides for the patch
94
+ * `checkJs` pass when {@link PatchLintConfig.checkJsStrict} is true.
95
+ * Merged after the strict preset; only boolean flags — no `paths`,
96
+ * `rootDir`, or other options that would fight the synthetic program.
97
+ */
98
+ export interface PatchLintCheckJsCompilerOptions {
99
+ strictNullChecks?: boolean;
100
+ strictFunctionTypes?: boolean;
101
+ strictBindCallApply?: boolean;
102
+ noImplicitThis?: boolean;
103
+ useUnknownInCatchVariables?: boolean;
104
+ strictPropertyInitialization?: boolean;
105
+ noUnusedLocals?: boolean;
106
+ noUnusedParameters?: boolean;
107
+ }
92
108
  /**
93
109
  * Configuration for patch lint rules.
94
110
  */
95
111
  export interface PatchLintConfig {
96
112
  /** Enable TypeScript checkJs pass on patch-owned .sys.mjs files */
97
113
  checkJs?: boolean;
114
+ /**
115
+ * When true with `checkJs: true`, run checkJs with `strict` and
116
+ * `noImplicitAny` enabled (CI-style). Default false preserves the
117
+ * historical loose preset. Optional {@link checkJsCompilerOptions}
118
+ * can relax individual strict flags (e.g. `strictNullChecks: false`).
119
+ */
120
+ checkJsStrict?: boolean;
121
+ /**
122
+ * Boolean overrides merged after the strict preset; only valid when
123
+ * `checkJsStrict` is true. Requires `checkJs: true`.
124
+ */
125
+ checkJsCompilerOptions?: PatchLintCheckJsCompilerOptions;
98
126
  /**
99
127
  * Project-relative path to an additional `.d.ts` file whose contents
100
128
  * are concatenated to the built-in `FIREFOX_GLOBALS_SHIM` for the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.18.11",
3
+ "version": "0.19.1",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",