From cb52ba6486b19363b5333b4e5c68264e04e77830 Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Mon, 13 Apr 2026 17:03:20 +0100 Subject: [PATCH] Refactoring: Split up script --- pr-checks/update-builtin-languages.ts | 160 ++++++++++++++++---------- 1 file changed, 99 insertions(+), 61 deletions(-) diff --git a/pr-checks/update-builtin-languages.ts b/pr-checks/update-builtin-languages.ts index 25baff936..45794cb0a 100644 --- a/pr-checks/update-builtin-languages.ts +++ b/pr-checks/update-builtin-languages.ts @@ -1,6 +1,6 @@ #!/usr/bin/env npx tsx -/** +/* * Updates src/languages/builtin.json by querying the CodeQL CLI for: * - Languages that have default queries (via codeql-extractor.yml) * - Language aliases (via `codeql resolve languages --format=betterjson --extractor-include-aliases`) @@ -19,69 +19,107 @@ import * as yaml from "yaml"; import { BUILTIN_LANGUAGES_FILE } from "./config"; -const codeqlPath = process.argv[2] || "codeql"; - -// Step 1: Resolve all language extractor directories. -const resolveJson: Record = JSON.parse( - execFileSync(codeqlPath, ["resolve", "languages", "--format=json"], { - encoding: "utf8", - }), -); - -// Step 2: For each language, read codeql-extractor.yml and check default_queries. -const languages: string[] = []; - -for (const [language, dirs] of Object.entries(resolveJson)) { - const extractorDir = dirs[0]; - const extractorYmlPath = path.join(extractorDir, "codeql-extractor.yml"); - - if (!fs.existsSync(extractorYmlPath)) { - throw new Error( - `Extractor YAML not found for language '${language}' at expected path: ${extractorYmlPath}`, - ); - } - - const extractorYml = yaml.parse(fs.readFileSync(extractorYmlPath, "utf8")); - const defaultQueries: unknown[] | undefined = extractorYml.default_queries; - - if (Array.isArray(defaultQueries) && defaultQueries.length > 0) { - console.log( - ` ✅ ${language}: included (default queries: ${JSON.stringify(defaultQueries)})`, - ); - languages.push(language); - } else { - console.log(` ❌ ${language}: excluded (no default queries)`); - } +/** Resolve all known language extractor directories. */ +function resolveLanguages(codeqlPath: string): Record { + return JSON.parse( + execFileSync(codeqlPath, ["resolve", "languages", "--format=json"], { + encoding: "utf8", + }), + ) as Record; } -languages.sort(); +/** + * Return the sorted list of languages whose extractors ship default queries. + * + * @param extractorDirs - Map from language to list of extractor directories + */ +function findLanguagesWithDefaultQueries( + extractorDirs: Record, +): string[] { + const languages: string[] = []; -// Step 3: Resolve aliases, filtered to only those targeting included languages. -const betterjsonOutput = JSON.parse( - execFileSync( - codeqlPath, - [ - "resolve", - "languages", - "--format=betterjson", - "--extractor-include-aliases", - ], - { encoding: "utf8" }, - ), -); + for (const [language, dirs] of Object.entries(extractorDirs)) { + if (dirs.length !== 1) { + throw new Error( + `Expected exactly one extractor directory for language '${language}', but found ${dirs.length}: ${dirs.join( + ", ", + )}`, + ); + } -const languageSet = new Set(languages); -const aliases: Record = Object.fromEntries( - Object.entries((betterjsonOutput.aliases ?? {}) as Record) - .filter(([, target]) => languageSet.has(target)) - .sort(([a], [b]) => a.localeCompare(b)), -); + const extractorYmlPath = path.join(dirs[0], "codeql-extractor.yml"); -// Step 4: Update the built-in languages file. -const content = `${JSON.stringify({ languages, aliases }, null, 2)}\n`; -fs.mkdirSync(path.dirname(BUILTIN_LANGUAGES_FILE), { recursive: true }); -fs.writeFileSync(BUILTIN_LANGUAGES_FILE, content); + if (!fs.existsSync(extractorYmlPath)) { + throw new Error( + `Extractor YAML not found for language '${language}' at expected path: ${extractorYmlPath}`, + ); + } -console.log(`\nWrote ${BUILTIN_LANGUAGES_FILE}`); -console.log(` Languages: ${languages.join(", ")}`); -console.log(` Aliases: ${Object.keys(aliases).join(", ")}`); + const extractorYml = yaml.parse(fs.readFileSync(extractorYmlPath, "utf8")); + const defaultQueries: unknown[] | undefined = extractorYml.default_queries; + + if (Array.isArray(defaultQueries) && defaultQueries.length > 0) { + console.log( + ` ✅ ${language}: included (default queries: ${JSON.stringify(defaultQueries)})`, + ); + languages.push(language); + } else { + console.log(` ❌ ${language}: excluded (no default queries)`); + } + } + + return languages.sort(); +} + +/** + * Resolve language aliases from the CodeQL CLI, keeping only those whose + * target is in the given set of included languages. + */ +function resolveAliases( + codeqlPath: string, + includedLanguages: Set, +): Record { + const betterjsonOutput = JSON.parse( + execFileSync( + codeqlPath, + [ + "resolve", + "languages", + "--format=betterjson", + "--extractor-include-aliases", + ], + { encoding: "utf8" }, + ), + ); + + return Object.fromEntries( + Object.entries((betterjsonOutput.aliases ?? {}) as Record) + .filter(([, target]) => includedLanguages.has(target)) + .sort(([a], [b]) => a.localeCompare(b)), + ); +} + +/** Write the built-in languages data to disk. */ +function writeBuiltinLanguages( + languages: string[], + aliases: Record, +): void { + const content = `${JSON.stringify({ languages, aliases }, null, 2)}\n`; + fs.mkdirSync(path.dirname(BUILTIN_LANGUAGES_FILE), { recursive: true }); + fs.writeFileSync(BUILTIN_LANGUAGES_FILE, content); + + console.log(`\nWrote ${BUILTIN_LANGUAGES_FILE}`); + console.log(` Languages: ${languages.join(", ")}`); + console.log(` Aliases: ${Object.keys(aliases).join(", ")}`); +} + +function main(): void { + const codeqlPath = process.argv[2] || "codeql"; + + const extractorDirs = resolveLanguages(codeqlPath); + const languages = findLanguagesWithDefaultQueries(extractorDirs); + const aliases = resolveAliases(codeqlPath, new Set(languages)); + writeBuiltinLanguages(languages, aliases); +} + +main();