Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
Expand Down Expand Up @@ -91,7 +91,7 @@ jobs:
exit 1

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
category: "/language:${{matrix.language}}"

17 changes: 15 additions & 2 deletions .github/workflows/job-compile-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,28 @@ jobs:
with:
node-version: 24

- name: Validate Yarn lockfile
run: yarn test-yarn-lock && yarn verify-yarn-lock
working-directory: Extension

- name: Install Dependencies
run: yarn install ${{ inputs.yarn-args }}
working-directory: Extension

- name: Install gdb (linux)
if: ${{ inputs.platform == 'linux' }}
timeout-minutes: 10
run: |
sudo apt-get update
sudo apt-get install -y gdb
sudo apt-get \
-o Acquire::Retries=3 \
-o Acquire::http::Timeout=30 \
-o Acquire::https::Timeout=30 \
update
sudo apt-get \
-o Acquire::Retries=3 \
-o Acquire::http::Timeout=30 \
-o Acquire::https::Timeout=30 \
install -y gdb

- name: Compile Sources
run: yarn run compile
Expand Down
1 change: 1 addition & 0 deletions Build/loc/TranslationsImportExport.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ extends:
- task: CmdLine@2
inputs:
script: 'cd Extension && yarn install'
retryCountOnTaskFailure: 3

- task: CmdLine@2
inputs:
Expand Down
1 change: 1 addition & 0 deletions Extension/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ bin/isense_driver
bin/libc.so
bin/LICENSE.txt
bin/scout_driver
bin/process_wait_test_helper
bin/unittests
bin/vcpkgsrvtest
bin/*.dll
Expand Down
9 changes: 7 additions & 2 deletions Extension/.scripts/import_edge_strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import * as fs from "fs";
import * as path from 'path';
import { parseString } from 'xml2js';
import { mkdir, write } from './common';
import { glob, mkdir, write } from './common';

export async function main() {

Expand Down Expand Up @@ -45,11 +45,16 @@ export async function main() {

const locFolderNames = fs.readdirSync(localizeRepoPath).filter(f => fs.lstatSync(path.join(localizeRepoPath, f)).isDirectory());
for (const locFolderName of locFolderNames) {
const lclPath = path.join(localizeRepoPath, locFolderName, "vc/vc/cpfeui.dll.lcl");
const languageInfo = languages.find(l => l.folderName === locFolderName);
if (!languageInfo) {
return;
}
const localePath = path.join(localizeRepoPath, locFolderName);
const lclPaths = await glob("vc/vc/cpfeui.dll.lcl", { cwd: localePath, nocase: true, nodir: true });
if (lclPaths.length !== 1) {
throw new Error(`Expected one cpfeui.dll.lcl under '${localePath}', found ${lclPaths.length}.`);
}
const lclPath = path.join(localePath, lclPaths[0]);
const languageId = languageInfo.id;
const outputLanguageFolder = path.join(cpptoolsRepoPath, "Extension/bin/messages", languageId);
const outputPath = path.join(outputLanguageFolder, "messages.json");
Expand Down
97 changes: 97 additions & 0 deletions Extension/.scripts/verifyYarnLock.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { fileURLToPath, pathToFileURL } from 'node:url';

const dependencySections = ['dependencies', 'devDependencies', 'optionalDependencies'];

function parseLockfileKey(key) {
const selectors = [];
let selectorStart = 0;
let quoted = false;
let escaped = false;

for (let index = 0; index < key.length; index++) {
const character = key[index];
if (escaped) {
escaped = false;
} else if (character === '\\' && quoted) {
escaped = true;
} else if (character === '"') {
quoted = !quoted;
} else if (character === ',' && !quoted) {
selectors.push(key.slice(selectorStart, index));
selectorStart = index + 1;
}
}
selectors.push(key.slice(selectorStart));

return selectors.map(selector => {
const trimmedSelector = selector.trim();
return trimmedSelector.startsWith('"') ? JSON.parse(trimmedSelector) : trimmedSelector;
});
}

function parseLockfileSelectors(lockfile) {
const selectors = new Set();
for (const line of lockfile.split(/\r?\n/)) {
if (/^[^\s#].*:\s*$/.test(line)) {
for (const selector of parseLockfileKey(line.replace(/:\s*$/, ''))) {
selectors.add(selector);
}
}
}
return selectors;
}

function getResolutionPackageName(pattern) {
const segments = pattern.split('/');
const packageName = segments.at(-1);
const scope = segments.at(-2);
return scope?.startsWith('@') ? `${scope}/${packageName}` : packageName;
}

function getExpectedSelectors(manifest) {
const selectors = [];
for (const section of dependencySections) {
for (const [packageName, range] of Object.entries(manifest[section] ?? {})) {
selectors.push(`${packageName}@${range}`);
}
}
for (const [pattern, range] of Object.entries(manifest.resolutions ?? {})) {
selectors.push(`${getResolutionPackageName(pattern)}@${range}`);
}
return selectors;
}

function findMissingSelectors(manifest, lockfile) {
const lockfileSelectors = parseLockfileSelectors(lockfile);
return getExpectedSelectors(manifest)
.filter(selector => !lockfileSelectors.has(selector))
.sort();
}

function validateYarnLock(packageJsonPath, yarnLockPath) {
const manifest = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const lockfile = fs.readFileSync(yarnLockPath, 'utf8');
const missingSelectors = findMissingSelectors(manifest, lockfile);
if (missingSelectors.length > 0) {
throw new Error(`yarn.lock is missing selectors required by package.json:\n${missingSelectors.map(selector => ` ${selector}`).join('\n')}\nRun yarn install to update yarn.lock.`);
}
}

const invokedUrl = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : undefined;
if (invokedUrl === import.meta.url) {
const extensionRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const packageJsonPath = process.argv[2] ?? path.join(extensionRoot, 'package.json');
const yarnLockPath = process.argv[3] ?? path.join(extensionRoot, 'yarn.lock');

try {
validateYarnLock(packageJsonPath, yarnLockPath);
} catch (error) {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
process.exitCode = 1;
}
}

export { findMissingSelectors, getExpectedSelectors, parseLockfileSelectors, validateYarnLock };
41 changes: 41 additions & 0 deletions Extension/.scripts/verifyYarnLock.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { findMissingSelectors } from './verifyYarnLock.mjs';

test('reports a stale resolution selector', () => {
const manifest = { resolutions: { 'fast-uri': '^3.1.5' } };
const lockfile = `fast-uri@^3.0.1, fast-uri@^3.1.4:
version "3.1.5"
`;

assert.deepEqual(findMissingSelectors(manifest, lockfile), ['fast-uri@^3.1.5']);
});

test('accepts direct, scoped, and nested resolution selectors', () => {
const manifest = {
dependencies: { '@scope/direct': '^1.0.0' },
devDependencies: { 'gulp-typescript': '^5.0.1' },
resolutions: {
'@scope/resolved': '^2.0.0',
'gulp-typescript/**/glob-parent': '^5.1.2',
'parent/**/@nested/package': '~3.0.0'
}
};
const lockfile = `"@nested/package@~3.0.0":
version "3.0.1"

"@scope/direct@^1.0.0":
version "1.0.0"

"@scope/resolved@^2.0.0":
version "2.0.0"

glob-parent@^3.1.0, glob-parent@^5.1.2:
version "5.1.2"

gulp-typescript@^5.0.1:
version "5.0.1"
`;

assert.deepEqual(findMissingSelectors(manifest, lockfile), []);
});
Loading
Loading