diff --git a/.github/workflows/codescanning-config-cli.yml b/.github/workflows/codescanning-config-cli.yml index 0c4829339..38b5b6ecc 100644 --- a/.github/workflows/codescanning-config-cli.yml +++ b/.github/workflows/codescanning-config-cli.yml @@ -6,13 +6,6 @@ env: # Diff informed queries add an additional query filter which is not yet # taken into account by these tests. CODEQL_ACTION_DIFF_INFORMED_QUERIES: false - # Specify overlay enablement manually to ensure stability around the exclude-from-incremental - # query filter. Here we only enable for the default code scanning suite. - CODEQL_ACTION_OVERLAY_ANALYSIS: true - CODEQL_ACTION_OVERLAY_ANALYSIS_JAVASCRIPT: false - CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_JAVASCRIPT: true - CODEQL_ACTION_OVERLAY_ANALYSIS_STATUS_CHECK: false - CODEQL_ACTION_OVERLAY_ANALYSIS_SKIP_RESOURCE_CHECKS: true on: push: @@ -79,33 +72,13 @@ jobs: with: version: ${{ matrix.version }} - # On PRs, overlay analysis may change the config that is passed to the CLI. - # Therefore, we have two variants of the following test, one for PRs and one for other events. - - name: Empty file (non-PR) - if: github.event_name != 'pull_request' + - name: Empty file uses: ./../action/.github/actions/check-codescanning-config with: expected-config-file-contents: "{}" languages: javascript tools: ${{ steps.prepare-test.outputs.tools-url }} - - name: Empty file (PR) - if: github.event_name == 'pull_request' - uses: ./../action/.github/actions/check-codescanning-config - with: - expected-config-file-contents: | - { - "query-filters": [ - { - "exclude": { - "tags": "exclude-from-incremental" - } - } - ] - } - languages: javascript - tools: ${{ steps.prepare-test.outputs.tools-url }} - - name: Packs from input if: success() || failure() uses: ./../action/.github/actions/check-codescanning-config diff --git a/.github/workflows/post-release-mergeback.yml b/.github/workflows/post-release-mergeback.yml index 5e1b3c3cd..50b31d1f5 100644 --- a/.github/workflows/post-release-mergeback.yml +++ b/.github/workflows/post-release-mergeback.yml @@ -131,7 +131,7 @@ jobs: echo "::endgroup::" - name: Generate token - uses: actions/create-github-app-token@v3.1.1 + uses: actions/create-github-app-token@v3.2.0 id: app-token with: app-id: ${{ vars.AUTOMATION_APP_ID }} diff --git a/.github/workflows/rollback-release.yml b/.github/workflows/rollback-release.yml index 65eac6c7b..ba10430f7 100644 --- a/.github/workflows/rollback-release.yml +++ b/.github/workflows/rollback-release.yml @@ -136,7 +136,7 @@ jobs: - name: Generate token if: github.event_name == 'workflow_dispatch' - uses: actions/create-github-app-token@v3.1.1 + uses: actions/create-github-app-token@v3.2.0 id: app-token with: app-id: ${{ vars.AUTOMATION_APP_ID }} diff --git a/.github/workflows/update-release-branch.yml b/.github/workflows/update-release-branch.yml index 991b4ae9a..147689ace 100644 --- a/.github/workflows/update-release-branch.yml +++ b/.github/workflows/update-release-branch.yml @@ -93,7 +93,7 @@ jobs: pull-requests: write # needed to create pull request steps: - name: Generate token - uses: actions/create-github-app-token@v3.1.1 + uses: actions/create-github-app-token@v3.2.0 id: app-token with: app-id: ${{ vars.AUTOMATION_APP_ID }} diff --git a/CHANGELOG.md b/CHANGELOG.md index d64c59b53..53ad4d765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th ## [UNRELEASED] +- For performance and accuracy reasons, [improved incremental analysis](https://github.com/github/roadmap/issues/1158) will now only be enabled on a pull request when diff-informed analysis is also enabled for that run. If diff-informed analysis is unavailable (for example, because the PR diff ranges could not be computed), the action will fall back to a full analysis. [#3791](https://github.com/github/codeql-action/pull/3791) - If multiple inputs are provided for the GitHub-internal `analysis-kinds` input, only `code-scanning` will be enabled. The `analysis-kinds` input is experimental, for GitHub-internal use only, and may change without notice at any time. [#3892](https://github.com/github/codeql-action/pull/3892) - Added an experimental change which, when running a Code Scanning analysis for a PR with [improved incremental analysis](https://github.com/github/roadmap/issues/1158) enabled, prefers CodeQL CLI versions that have a cached overlay-base database for the configured languages. This speeds up analysis for a repository when there is not yet a cached overlay-base database for the latest CLI version. We expect to roll this change out to everyone in May. [#3880](https://github.com/github/codeql-action/pull/3880) diff --git a/lib/entry-points.js b/lib/entry-points.js index ee4eaaae1..43e961f71 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -150818,9 +150818,6 @@ function makeTelemetryDiagnostic(id, name, attributes) { // src/diff-informed-analysis-utils.ts var fs6 = __toESM(require("fs")); -async function shouldPerformDiffInformedAnalysis(codeql, features, logger) { - return await getDiffInformedAnalysisBranches(codeql, features, logger) !== void 0; -} async function getDiffInformedAnalysisBranches(codeql, features, logger) { if (!await features.getValue("diff_informed_queries" /* DiffInformedQueries */, codeql)) { return void 0; @@ -150837,6 +150834,30 @@ async function getDiffInformedAnalysisBranches(codeql, features, logger) { } return branches; } +async function prepareDiffInformedAnalysis(codeql, features, logger) { + let branches; + try { + branches = await getDiffInformedAnalysisBranches(codeql, features, logger); + } catch (e) { + logger.warning( + `Failed to determine branch information for diff-informed analysis: ${getErrorMessage(e)}` + ); + return false; + } + if (!branches) { + return false; + } + return await withGroupAsync("Computing PR diff ranges", async () => { + try { + return await computeAndPersistDiffRanges(branches, logger); + } catch (e) { + logger.warning( + `Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}` + ); + return false; + } + }); +} function writeDiffRangesJsonFile(logger, ranges) { const jsonContents = JSON.stringify(ranges, null, 2); const jsonFilePath = getDiffRangesJsonFilePath(); @@ -150887,6 +150908,18 @@ async function getPullRequestEditedDiffRanges(branches, logger) { } return results; } +async function computeAndPersistDiffRanges(branches, logger) { + const ranges = await getPullRequestEditedDiffRanges(branches, logger); + if (ranges === void 0) { + return false; + } + writeDiffRangesJsonFile(logger, ranges); + const distinctFiles = new Set(ranges.map((r) => r.path)).size; + logger.info( + `Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).` + ); + return true; +} async function getFileDiffsWithBasehead(branches, logger) { const repositoryNwo = getRepositoryNwoFromEnv( "CODE_SCANNING_REPOSITORY", @@ -151891,6 +151924,25 @@ function userConfigFromActionPath(tempDir) { function hasQueryCustomisation(userConfig) { return isDefined2(userConfig["disable-default-queries"]) || isDefined2(userConfig.queries) || isDefined2(userConfig["query-filters"]); } +async function applyIncrementalAnalysisSettings(config, hasDiffRanges, codeql, logger) { + if (config.overlayDatabaseMode === "overlay" /* Overlay */ && !hasDiffRanges) { + logger.info( + `Reverting overlay database mode to ${"none" /* None */} because the PR diff ranges could not be computed.` + ); + config.overlayDatabaseMode = "none" /* None */; + config.useOverlayDatabaseCaching = false; + await addOverlayDisablementDiagnostics( + config, + codeql, + "diff-informed-analysis-not-enabled" /* DiffInformedAnalysisNotEnabled */ + ); + } + if (hasDiffRanges) { + config.extraQueryExclusions.push({ + exclude: { tags: "exclude-from-incremental" } + }); + } +} async function initConfig(features, inputs) { const { logger, tempDir } = inputs; if (inputs.configInput) { @@ -152000,15 +152052,17 @@ async function initConfig(features, inputs) { overlayDisabledReason ); } - if (config.overlayDatabaseMode === "overlay" /* Overlay */ || await shouldPerformDiffInformedAnalysis( + const hasDiffRanges = await prepareDiffInformedAnalysis( inputs.codeql, inputs.features, logger - )) { - config.extraQueryExclusions.push({ - exclude: { tags: "exclude-from-incremental" } - }); - } + ); + await applyIncrementalAnalysisSettings( + config, + hasDiffRanges, + inputs.codeql, + logger + ); if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) { const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( inputs.codeql, @@ -159063,7 +159117,6 @@ async function run3(startedAt) { logFileCoverageOnPrsDeprecationWarning(logger); } await checkInstallPython311(config.languages, codeql); - await computeAndPersistDiffRanges(codeql, features, logger); } catch (unwrappedError) { const error3 = wrapError(unwrappedError); core21.setFailed(error3.message); @@ -159336,33 +159389,6 @@ async function loadRepositoryProperties(repositoryNwo, logger) { return new Failure(error3); } } -async function computeAndPersistDiffRanges(codeql, features, logger) { - await withGroupAsync("Computing PR diff ranges", async () => { - try { - const branches = await getDiffInformedAnalysisBranches( - codeql, - features, - logger - ); - if (!branches) { - return; - } - const ranges = await getPullRequestEditedDiffRanges(branches, logger); - if (ranges === void 0) { - return; - } - writeDiffRangesJsonFile(logger, ranges); - const distinctFiles = new Set(ranges.map((r) => r.path)).size; - logger.info( - `Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).` - ); - } catch (e) { - logger.warning( - `Failed to compute and persist PR diff ranges: ${getErrorMessage(e)}` - ); - } - }); -} async function recordZstdAvailability(config, zstdAvailability) { addNoLanguageDiagnostic( config, diff --git a/package-lock.json b/package-lock.json index 2692749f8..48052b773 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,11 +57,11 @@ "eslint-plugin-jsdoc": "^62.9.0", "eslint-plugin-no-async-foreach": "^0.1.1", "glob": "^11.1.0", - "globals": "^17.5.0", + "globals": "^17.6.0", "nock": "^14.0.12", "sinon": "^21.1.2", "typescript": "^6.0.3", - "typescript-eslint": "^8.59.1" + "typescript-eslint": "^8.59.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2528,17 +2528,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", - "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", + "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.1", - "@typescript-eslint/type-utils": "8.59.1", - "@typescript-eslint/utils": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/type-utils": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2551,7 +2551,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.1", + "@typescript-eslint/parser": "^8.59.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -2567,16 +2567,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", - "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz", + "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", "debug": "^4.4.3" }, "engines": { @@ -2610,14 +2610,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", - "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz", + "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.1", - "@typescript-eslint/types": "^8.59.1", + "@typescript-eslint/tsconfig-utils": "^8.59.2", + "@typescript-eslint/types": "^8.59.2", "debug": "^4.4.3" }, "engines": { @@ -2650,14 +2650,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", - "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz", + "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1" + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2668,9 +2668,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", - "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz", + "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==", "dev": true, "license": "MIT", "engines": { @@ -2685,15 +2685,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", - "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz", + "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1", - "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2728,9 +2728,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", - "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz", + "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==", "dev": true, "license": "MIT", "engines": { @@ -2742,16 +2742,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", - "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz", + "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.1", - "@typescript-eslint/tsconfig-utils": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/visitor-keys": "8.59.1", + "@typescript-eslint/project-service": "8.59.2", + "@typescript-eslint/tsconfig-utils": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2780,9 +2780,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -2827,16 +2827,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", - "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz", + "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.1", - "@typescript-eslint/types": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1" + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2851,13 +2851,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", - "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz", + "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/types": "8.59.2", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -6121,9 +6121,9 @@ } }, "node_modules/globals": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", - "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true, "license": "MIT", "engines": { @@ -9789,16 +9789,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.59.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", - "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.2.tgz", + "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.1", - "@typescript-eslint/parser": "8.59.1", - "@typescript-eslint/typescript-estree": "8.59.1", - "@typescript-eslint/utils": "8.59.1" + "@typescript-eslint/eslint-plugin": "8.59.2", + "@typescript-eslint/parser": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10261,9 +10261,9 @@ } }, "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -10404,7 +10404,7 @@ "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": ">=9.2.2", "@octokit/plugin-rest-endpoint-methods": "^17.0.0", - "yaml": "^2.8.3" + "yaml": "^2.8.4" }, "devDependencies": { "@types/node": "^20.19.39", diff --git a/package.json b/package.json index 3bd6d87ec..d46e50792 100644 --- a/package.json +++ b/package.json @@ -64,11 +64,11 @@ "eslint-plugin-jsdoc": "^62.9.0", "eslint-plugin-no-async-foreach": "^0.1.1", "glob": "^11.1.0", - "globals": "^17.5.0", + "globals": "^17.6.0", "nock": "^14.0.12", "sinon": "^21.1.2", "typescript": "^6.0.3", - "typescript-eslint": "^8.59.1" + "typescript-eslint": "^8.59.2" }, "overrides": { "@actions/tool-cache": { diff --git a/pr-checks/package.json b/pr-checks/package.json index 0189318ed..2741560f6 100644 --- a/pr-checks/package.json +++ b/pr-checks/package.json @@ -7,7 +7,7 @@ "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": ">=9.2.2", "@octokit/plugin-rest-endpoint-methods": "^17.0.0", - "yaml": "^2.8.3" + "yaml": "^2.8.4" }, "devDependencies": { "@types/node": "^20.19.39", diff --git a/src/artifact-scanner.test.ts b/src/artifact-scanner.test.ts index 6f68e647d..56f99e113 100644 --- a/src/artifact-scanner.test.ts +++ b/src/artifact-scanner.test.ts @@ -141,14 +141,9 @@ test("scanArtifactsForTokens handles files without tokens", async (t) => { } }); -// This test is slow (extracts and scans a zip artifact), so by default we only run it in CI. Set -// RUN_SLOW_TESTS=1 to run it locally. -if ( - os.platform() !== "win32" && - (process.env.CI === "true" || process.env.RUN_SLOW_TESTS === "1") -) { +// `scanArchiveFile` does not support Windows, so we skip this test there. +if (os.platform() !== "win32") { test("scanArtifactsForTokens finds token in debug artifacts", async (t) => { - t.timeout(15000); // 15 seconds const messages: LoggedMessage[] = []; const logger = getRecordingLogger(messages, { logToConsole: false }); // The zip here is a regression test based on diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 8e36d45bf..10dee5539 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -21,6 +21,7 @@ import { GitVersionInfo } from "./git-utils"; import { BuiltInLanguage, Language } from "./languages"; import { getRunnerLogger } from "./logging"; import { CODEQL_OVERLAY_MINIMUM_VERSION } from "./overlay"; +import * as overlayDiagnostics from "./overlay/diagnostics"; import { OverlayDisabledReason } from "./overlay/diagnostics"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import * as overlayStatus from "./overlay/status"; @@ -2143,3 +2144,87 @@ test.serial( }); }, ); + +test("applyIncrementalAnalysisSettings: no-op when mode is not Overlay and diff ranges are unavailable", async (t) => { + const config = createTestConfig({}); + config.overlayDatabaseMode = OverlayDatabaseMode.None; + const codeql = createStubCodeQL({}); + const logger = getRunnerLogger(true); + + await configUtils.applyIncrementalAnalysisSettings( + config, + false, + codeql, + logger, + ); + + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None); + t.deepEqual(config.extraQueryExclusions, []); +}); + +test("applyIncrementalAnalysisSettings: keeps overlay mode and adds exclusions when diff ranges are available", async (t) => { + const config = createTestConfig({ + overlayDatabaseMode: OverlayDatabaseMode.Overlay, + }); + const codeql = createStubCodeQL({}); + const logger = getRunnerLogger(true); + + await configUtils.applyIncrementalAnalysisSettings( + config, + true, + codeql, + logger, + ); + + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.Overlay); + t.deepEqual(config.extraQueryExclusions, [ + { exclude: { tags: "exclude-from-incremental" } }, + ]); +}); + +test("applyIncrementalAnalysisSettings: disables overlay analysis when diff ranges are unavailable", async (t) => { + const config = createTestConfig({ + overlayDatabaseMode: OverlayDatabaseMode.Overlay, + }); + config.useOverlayDatabaseCaching = true; + const codeql = createStubCodeQL({}); + const logger = getRunnerLogger(true); + const addDiagnosticsStub = sinon + .stub(overlayDiagnostics, "addOverlayDisablementDiagnostics") + .resolves(); + + await configUtils.applyIncrementalAnalysisSettings( + config, + false, + codeql, + logger, + ); + + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None); + t.is(config.useOverlayDatabaseCaching, false); + t.deepEqual(config.extraQueryExclusions, []); + t.true(addDiagnosticsStub.calledOnce); + t.is( + addDiagnosticsStub.firstCall.args[2], + OverlayDisabledReason.DiffInformedAnalysisNotEnabled, + ); +}); + +test("applyIncrementalAnalysisSettings: adds exclusions for diff-informed-only runs", async (t) => { + const config = createTestConfig({}); + config.overlayDatabaseMode = OverlayDatabaseMode.None; + const codeql = createStubCodeQL({}); + const logger = getRunnerLogger(true); + + await configUtils.applyIncrementalAnalysisSettings( + config, + true, + codeql, + logger, + ); + + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None); + t.deepEqual(config.extraQueryExclusions, [ + { exclude: { tags: "exclude-from-incremental" } }, + ]); +}); diff --git a/src/config-utils.ts b/src/config-utils.ts index 9f5f61c4e..87329fce2 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -31,7 +31,7 @@ import { addNoLanguageDiagnostic, makeTelemetryDiagnostic, } from "./diagnostics"; -import { shouldPerformDiffInformedAnalysis } from "./diff-informed-analysis-utils"; +import { prepareDiffInformedAnalysis } from "./diff-informed-analysis-utils"; import { EnvVar } from "./environment"; import * as errorMessages from "./error-messages"; import { Feature, FeatureEnablement } from "./feature-flags"; @@ -1077,6 +1077,48 @@ function hasQueryCustomisation(userConfig: UserConfig): boolean { ); } +/** + * Finalize the incremental-analysis configuration for this run. + * + * Overlay analysis has only been validated in combination with diff-informed + * analysis, so if `Overlay` mode was selected for a pull request but the diff + * ranges could not be computed, fall back to a full non-overlay analysis. + * + * Query exclusions for incremental-only queries are then applied whenever the + * diff ranges are available — which, after the fallback above, is exactly the + * set of runs where any kind of incremental analysis (overlay or + * diff-informed) is in effect. + */ +export async function applyIncrementalAnalysisSettings( + config: Config, + hasDiffRanges: boolean, + codeql: CodeQL, + logger: Logger, +): Promise { + if ( + config.overlayDatabaseMode === OverlayDatabaseMode.Overlay && + !hasDiffRanges + ) { + logger.info( + `Reverting overlay database mode to ${OverlayDatabaseMode.None} ` + + "because the PR diff ranges could not be computed.", + ); + config.overlayDatabaseMode = OverlayDatabaseMode.None; + config.useOverlayDatabaseCaching = false; + await addOverlayDisablementDiagnostics( + config, + codeql, + OverlayDisabledReason.DiffInformedAnalysisNotEnabled, + ); + } + + if (hasDiffRanges) { + config.extraQueryExclusions.push({ + exclude: { tags: "exclude-from-incremental" }, + }); + } +} + /** * Load and return the config. * @@ -1231,18 +1273,18 @@ export async function initConfig( ); } - if ( - config.overlayDatabaseMode === OverlayDatabaseMode.Overlay || - (await shouldPerformDiffInformedAnalysis( - inputs.codeql, - inputs.features, - logger, - )) - ) { - config.extraQueryExclusions.push({ - exclude: { tags: "exclude-from-incremental" }, - }); - } + const hasDiffRanges = await prepareDiffInformedAnalysis( + inputs.codeql, + inputs.features, + logger, + ); + + await applyIncrementalAnalysisSettings( + config, + hasDiffRanges, + inputs.codeql, + logger, + ); if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) { const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime( diff --git a/src/diff-informed-analysis-utils.test.ts b/src/diff-informed-analysis-utils.test.ts index 0ea71db95..6d185e564 100644 --- a/src/diff-informed-analysis-utils.test.ts +++ b/src/diff-informed-analysis-utils.test.ts @@ -5,14 +5,16 @@ import * as actionsUtil from "./actions-util"; import type { PullRequestBranches } from "./actions-util"; import * as apiClient from "./api-client"; import { - shouldPerformDiffInformedAnalysis, + getDiffInformedAnalysisBranches, + prepareDiffInformedAnalysis, exportedForTesting, } from "./diff-informed-analysis-utils"; -import { Feature, initFeatures } from "./feature-flags"; +import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; import { getRunnerLogger } from "./logging"; import { parseRepositoryNwo } from "./repository"; import { setupTests, + createFeatures, mockCodeQLVersion, mockFeatureFlagApiEndpoint, setupActionsVars, @@ -80,13 +82,13 @@ const testShouldPerformDiffInformedAnalysis = makeMacro({ .stub(actionsUtil, "getPullRequestBranches") .returns(testCase.pullRequestBranches); - const result = await shouldPerformDiffInformedAnalysis( + const branches = await getDiffInformedAnalysisBranches( codeql, features, logger, ); - t.is(result, expectedResult); + t.is(branches !== undefined, expectedResult); delete process.env.CODEQL_ACTION_DIFF_INFORMED_QUERIES; @@ -94,7 +96,7 @@ const testShouldPerformDiffInformedAnalysis = makeMacro({ getPullRequestBranchesStub.restore(); }); }, - title: (title) => `shouldPerformDiffInformedAnalysis: ${title}`, + title: (title) => `getDiffInformedAnalysisBranches: ${title}`, }); testShouldPerformDiffInformedAnalysis.serial( @@ -178,6 +180,135 @@ testShouldPerformDiffInformedAnalysis.serial( false, ); +test.serial( + "prepareDiffInformedAnalysis: returns false when not a pull request", + async (t) => { + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + const logger = getRunnerLogger(true); + const codeql = mockCodeQLVersion("2.21.0"); + const features = createFeatures([Feature.DiffInformedQueries]); + + sinon.stub(actionsUtil, "getPullRequestBranches").returns(undefined); + sinon + .stub(apiClient, "getGitHubVersion") + .resolves({ type: GitHubVariant.DOTCOM }); + + const result = await prepareDiffInformedAnalysis( + codeql, + features, + logger, + ); + + t.false(result); + }); + }, +); + +test.serial( + "prepareDiffInformedAnalysis: returns false when applicability check throws", + async (t) => { + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + const logger = getRunnerLogger(true); + const codeql = mockCodeQLVersion("2.21.0"); + // A features implementation whose getValue rejects, simulating an + // unexpected failure when determining whether diff-informed analysis + // should run. + const features: FeatureEnablement = { + getEnabledDefaultCliVersions: async () => { + throw new Error("not implemented"); + }, + getValue: async () => { + throw new Error("feature flag lookup failed"); + }, + }; + + const result = await prepareDiffInformedAnalysis( + codeql, + features, + logger, + ); + + t.false(result); + }); + }, +); + +test.serial( + "prepareDiffInformedAnalysis: returns true when the diff is fetched successfully", + async (t) => { + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + const logger = getRunnerLogger(true); + const codeql = mockCodeQLVersion("2.21.0"); + const features = createFeatures([Feature.DiffInformedQueries]); + + sinon + .stub(actionsUtil, "getPullRequestBranches") + .returns({ base: "main", head: "feature" }); + sinon + .stub(apiClient, "getGitHubVersion") + .resolves({ type: GitHubVariant.DOTCOM }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + sinon.stub(apiClient, "getApiClient").returns({ + rest: { + repos: { + compareCommitsWithBasehead: sinon + .stub() + .resolves({ data: { files: [] } }), + }, + }, + } as any); + + const result = await prepareDiffInformedAnalysis( + codeql, + features, + logger, + ); + + t.true(result); + }); + }, +); + +test.serial( + "prepareDiffInformedAnalysis: returns false when the diff API call fails", + async (t) => { + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + const logger = getRunnerLogger(true); + const codeql = mockCodeQLVersion("2.21.0"); + const features = createFeatures([Feature.DiffInformedQueries]); + + sinon + .stub(actionsUtil, "getPullRequestBranches") + .returns({ base: "main", head: "feature" }); + sinon + .stub(apiClient, "getGitHubVersion") + .resolves({ type: GitHubVariant.DOTCOM }); + const notFoundError: any = new Error("Not Found"); + notFoundError.status = 404; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + sinon.stub(apiClient, "getApiClient").returns({ + rest: { + repos: { + compareCommitsWithBasehead: sinon.stub().rejects(notFoundError), + }, + }, + } as any); + + const result = await prepareDiffInformedAnalysis( + codeql, + features, + logger, + ); + + t.false(result); + }); + }, +); + function runGetDiffRanges(changes: number, patch: string[] | undefined): any { return exportedForTesting.getDiffRanges( { diff --git a/src/diff-informed-analysis-utils.ts b/src/diff-informed-analysis-utils.ts index 1c98d4cac..a48c6dcfd 100644 --- a/src/diff-informed-analysis-utils.ts +++ b/src/diff-informed-analysis-utils.ts @@ -5,9 +5,9 @@ import type { PullRequestBranches } from "./actions-util"; import { getApiClient, getGitHubVersion } from "./api-client"; import type { CodeQL } from "./codeql"; import { Feature, FeatureEnablement } from "./feature-flags"; -import { Logger } from "./logging"; +import { Logger, withGroupAsync } from "./logging"; import { getRepositoryNwoFromEnv } from "./repository"; -import { GitHubVariant, satisfiesGHESVersion } from "./util"; +import { getErrorMessage, GitHubVariant, satisfiesGHESVersion } from "./util"; /** * This interface is an abbreviated version of the file diff object returned by @@ -21,20 +21,6 @@ interface FileDiff { patch?: string | undefined; } -/** - * Check if the action should perform diff-informed analysis. - */ -export async function shouldPerformDiffInformedAnalysis( - codeql: CodeQL, - features: FeatureEnablement, - logger: Logger, -): Promise { - return ( - (await getDiffInformedAnalysisBranches(codeql, features, logger)) !== - undefined - ); -} - /** * Get the branches to use for diff-informed analysis. * @@ -69,6 +55,46 @@ export async function getDiffInformedAnalysisBranches( return branches; } +/** + * Prepares the diff ranges needed for diff-informed analysis for the current + * run. + * + * @returns `true` if the diff ranges were successfully computed and persisted + * and are therefore available for use, `false` otherwise. + */ +export async function prepareDiffInformedAnalysis( + codeql: CodeQL, + features: FeatureEnablement, + logger: Logger, +): Promise { + let branches: PullRequestBranches | undefined; + try { + branches = await getDiffInformedAnalysisBranches(codeql, features, logger); + } catch (e) { + // If we cannot determine whether diff-informed analysis applies (for + // example, because a feature-flag lookup failed), treat it as not + // applicable rather than triggering the overlay fallback. + logger.warning( + `Failed to determine branch information for diff-informed analysis: ${getErrorMessage(e)}`, + ); + return false; + } + if (!branches) { + return false; + } + + return await withGroupAsync("Computing PR diff ranges", async () => { + try { + return await computeAndPersistDiffRanges(branches, logger); + } catch (e) { + logger.warning( + `Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}`, + ); + return false; + } + }); +} + export interface DiffThunkRange { /** Relative path from the repository root, using forward slashes as separators. */ path: string; @@ -151,6 +177,33 @@ export async function getPullRequestEditedDiffRanges( return results; } +/** + * Compute and persist the diff ranges for a pull request. This fetches the + * diff from the GitHub API and writes it to the diff ranges JSON file so that + * CodeQL can use it for diff-informed analysis. + * + * @param branches The base and head branches of the pull request, as returned + * by `getDiffInformedAnalysisBranches`. + * @param logger + * @returns `true` if the diff ranges were successfully computed and persisted, + * otherwise `false`. + */ +export async function computeAndPersistDiffRanges( + branches: PullRequestBranches, + logger: Logger, +): Promise { + const ranges = await getPullRequestEditedDiffRanges(branches, logger); + if (ranges === undefined) { + return false; + } + writeDiffRangesJsonFile(logger, ranges); + const distinctFiles = new Set(ranges.map((r) => r.path)).size; + logger.info( + `Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).`, + ); + return true; +} + async function getFileDiffsWithBasehead( branches: PullRequestBranches, logger: Logger, diff --git a/src/init-action.ts b/src/init-action.ts index f8402177d..9d2619b1d 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -37,11 +37,6 @@ import { makeDiagnostic, makeTelemetryDiagnostic, } from "./diagnostics"; -import { - getDiffInformedAnalysisBranches, - getPullRequestEditedDiffRanges, - writeDiffRangesJsonFile, -} from "./diff-informed-analysis-utils"; import { EnvVar } from "./environment"; import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; import { @@ -434,7 +429,6 @@ async function run(startedAt: Date) { } await checkInstallPython311(config.languages, codeql); - await computeAndPersistDiffRanges(codeql, features, logger); } catch (unwrappedError) { const error = wrapError(unwrappedError); core.setFailed(error.message); @@ -830,42 +824,6 @@ async function loadRepositoryProperties( } } -/** - * Compute and persist diff ranges when diff-informed analysis is enabled - * (feature flag + PR context). This writes the standard pr-diff-range.json - * file for later reuse in the analyze step. Failures are logged but non-fatal. - */ -async function computeAndPersistDiffRanges( - codeql: CodeQL, - features: FeatureEnablement, - logger: Logger, -): Promise { - await withGroupAsync("Computing PR diff ranges", async () => { - try { - const branches = await getDiffInformedAnalysisBranches( - codeql, - features, - logger, - ); - if (!branches) { - return; - } - const ranges = await getPullRequestEditedDiffRanges(branches, logger); - if (ranges === undefined) { - return; - } - writeDiffRangesJsonFile(logger, ranges); - const distinctFiles = new Set(ranges.map((r) => r.path)).size; - logger.info( - `Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).`, - ); - } catch (e) { - logger.warning( - `Failed to compute and persist PR diff ranges: ${getErrorMessage(e)}`, - ); - } - }); -} async function recordZstdAvailability( config: configUtils.Config, zstdAvailability: ZstdAvailability, diff --git a/src/overlay/diagnostics.ts b/src/overlay/diagnostics.ts index 6bc11a73f..4b716c3df 100644 --- a/src/overlay/diagnostics.ts +++ b/src/overlay/diagnostics.ts @@ -39,6 +39,15 @@ export enum OverlayDisabledReason { NotPullRequestOrDefaultBranch = "not-pull-request-or-default-branch", /** The top-level overlay analysis feature flag is not enabled. */ OverallFeatureNotEnabled = "overall-feature-not-enabled", + /** + * Overlay analysis was selected for a pull request, but diff-informed + * analysis was not enabled for the run (for example, because the + * `DiffInformedQueries` feature flag is off, the GHES version is too old, + * or the PR diff ranges could not be computed). Overlay analysis has only + * been validated in combination with diff-informed analysis, so we fall + * back to a non-overlay analysis in this case. + */ + DiffInformedAnalysisNotEnabled = "diff-informed-analysis-not-enabled", /** Overlay analysis was skipped because it previously failed with similar hardware resources. */ SkippedDueToCachedStatus = "skipped-due-to-cached-status", /** Disk usage could not be determined during the overlay status check. */ diff --git a/src/testdata/debug-artifacts-with-fake-token.zip b/src/testdata/debug-artifacts-with-fake-token.zip index 09a60be41..5a121691a 100644 Binary files a/src/testdata/debug-artifacts-with-fake-token.zip and b/src/testdata/debug-artifacts-with-fake-token.zip differ