GitNexus/gitnexus/test/helpers/ignore-set-source.ts
Gergő Magyar 56feb85c97
Some checks are pending
CodeQL / Analyze (python) (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
chore: compile first-party packages with TypeScript 7 (#3311)
* fix(web): drop TypeScript 7-incompatible tsconfig paths

Remove baseUrl and the dead ../shared include so web project references typecheck under TypeScript 7.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(cli): parse TypeScript with a TypeScript 6 API package

Keep AST guards working after the named typescript package becomes 7, which no longer ships the Compiler API.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(lint): pin root TypeScript to the 6 API package

Give typescript-eslint a TypeScript 6 peer so syntax-only lint still installs after CLI and web move to TypeScript 7.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(deps): compile first-party packages with TypeScript 7.0.2

Unify CLI and web on the same native compiler line as gitnexus-shared so typecheck and emit no longer split 5.x versus 7.x.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(ci): describe parent TypeScript 7 as the shared compiler

Stop saying web compiles shared with TypeScript 5 now that the parent lockfile is 7.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): compile shared from parent TypeScript on Vercel and skill-evolution

Stop isolated npm installs in gitnexus-shared so those paths do not pull a second TypeScript 7 optional-platform tree.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: record TypeScript 7 typecheck and Dependabot major-split policy

Keep contributor typecheck commands, and stop Dependabot from bumping shared onto a different TypeScript major than CLI and web.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(lint): pin root TypeScript to 5.9 so npm ci satisfies eslint peers

typescript-eslint 8 peers typescript below 6.0.0, so the typescript6 alias made quality lint npm ci fail with ERESOLVE.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(cli): drop the TypeScript 6 Compiler API package

TypeScript 7.0 has no classic createProgram surface, so parse-only
guards now use Babel and Mode 4 uses the TypeScript 7 Checker.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: align contributor setup with parent TypeScript 7 compile

Stop telling clones to npm-install gitnexus-shared; CI and Vercel already emit that package from a parent lib/tsc.js shim.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): typecheck React JSX on TypeScript 7 with explicit DOM libs

TypeScript 7 no longer implies DOM or auto-includes @types, so the web app must declare React/JSX settings while Vite keeps plugin-react.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: pin Vercel --include=dev and share parse-only string helpers

Production npm ci omits the web TypeScript unless --include=dev is on that install. Move staticStringValue next to the other Babel walk helpers so CLI help and contract tests share one source.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(autofix): apply prettier + eslint fixes via /autofix command

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-17 22:16:00 +01:00

105 lines
4.1 KiB
TypeScript

/**
* Reads the bare-name sets in `src/config/ignore-service.ts` out of source.
*
* Those sets are module-private, and exporting them purely to be testable would
* widen a production surface to satisfy a test — the call
* `receiver-twin-list-drift.test.ts` documents. So the guards read the source
* instead, through `@babel/parser` (`parse-typescript-source.ts`).
*
* Using an AST parser for TypeScript syntax is what makes the guards
* trustworthy. A text scanner has to decide whether a delimiter opens a comment
* or sits inside a string, and it gets that wrong in both directions here: the
* ignore-list comments quote paths and carry an apostrophe (`Next.js's`), while
* a glob string such as `'** / *'` contains a comment-open sequence. It also
* has to guess which bracket belongs to the declaration rather than to a type
* annotation. Each of those is a way to silently read fewer members — and a
* guard that quietly stops seeing members is the exact defect these guards
* exist to catch.
*
* `setEntries` therefore refuses anything that is not a plain list of string
* literals, rather than skipping the members it cannot resolve.
*/
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import * as t from '@babel/types';
import { forEachChild, nodeText, parseTypeScript } from './parse-typescript-source.js';
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
/** The analyzer's ignore rules — the sets every guard in this family reads. */
export const IGNORE_SERVICE_PATH = path.join(
REPO_ROOT,
'gitnexus',
'src',
'config',
'ignore-service.ts',
);
/** The browser upload pre-filter, whose excluded-directory set must not drift from the above. */
export const UPLOAD_FILTER_PATH = path.join(
REPO_ROOT,
'gitnexus-web',
'src',
'lib',
'upload-filter.ts',
);
export const readSource = (file: string): string => readFileSync(file, 'utf8');
/**
* The string literals `setName` is constructed from, in declaration order.
*
* Throws — never returns a short list — when the declaration is missing or holds
* anything other than plain string literals (a spread, an interpolation, a
* concatenation, a computed value).
*/
export const setEntries = (source: string, setName: string): string[] => {
const { ast } = parseTypeScript('ignore-set-source.ts', source);
let elements: t.ArrayExpression['elements'] | undefined;
const visit = (node: t.Node): void => {
if (
elements === undefined &&
t.isVariableDeclarator(node) &&
t.isIdentifier(node.id) &&
node.id.name === setName &&
node.init !== undefined &&
node.init !== null &&
t.isNewExpression(node.init) &&
node.init.arguments.length === 1 &&
t.isArrayExpression(node.init.arguments[0])
) {
elements = node.init.arguments[0].elements;
return;
}
forEachChild(node, visit);
};
visit(ast);
if (elements === undefined) {
throw new Error(`${setName} is not declared as \`new Set([...])\` — update this test`);
}
const unresolvable = elements.filter((element) => !t.isStringLiteral(element));
if (unresolvable.length > 0) {
const first = unresolvable[0];
const excerpt = first && typeof first === 'object' ? nodeText(source, first) : String(first);
throw new Error(
`${setName} holds ${unresolvable.length} member(s) that are not plain string literals ` +
`(first: \`${excerpt}\`). A source-reading guard cannot resolve ` +
`those, so switch this set to a runtime assertion rather than letting the guard see fewer members.`,
);
}
return elements.map((element) => (element as t.StringLiteral).value);
};
/**
* True when `setName` is mutated by `.add(...)` anywhere in `source`.
*
* `setEntries` reads the declaration only, so a member appended afterwards would
* be invisible to it. The guards assert this is false rather than under-reporting.
*/
export const hasRuntimeAdd = (source: string, setName: string): boolean =>
new RegExp(`\\b${setName}\\s*\\.\\s*add\\s*\\(`).test(source);