mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
Make sure API team is aware of changes to contributes too (#294624)
This commit is contained in:
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -17,3 +17,4 @@ build/lib/policies/policyData.jsonc @joshspicer @rebornix @joaomoreno @pwang347
|
|||||||
# Ensure the API team is aware of changes to the vscode-dts file
|
# Ensure the API team is aware of changes to the vscode-dts file
|
||||||
# this is only about the final API, not about proposed API changes
|
# this is only about the final API, not about proposed API changes
|
||||||
src/vscode-dts/vscode.d.ts @jrieken @mjbvz @alexr00
|
src/vscode-dts/vscode.d.ts @jrieken @mjbvz @alexr00
|
||||||
|
src/vs/workbench/services/extensions/common/extensionPoints.json @jrieken @mjbvz @alexr00
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ import * as task from './lib/task.ts';
|
|||||||
import * as util from './lib/util.ts';
|
import * as util from './lib/util.ts';
|
||||||
import { useEsbuildTranspile } from './buildConfig.ts';
|
import { useEsbuildTranspile } from './buildConfig.ts';
|
||||||
|
|
||||||
|
// Extension point names
|
||||||
|
gulp.task(compilation.compileExtensionPointNamesTask);
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
// API proposal names
|
// API proposal names
|
||||||
@@ -30,12 +33,12 @@ const transpileClientTask = task.define('transpile-client', task.series(util.rim
|
|||||||
gulp.task(transpileClientTask);
|
gulp.task(transpileClientTask);
|
||||||
|
|
||||||
// Fast compile for development time
|
// Fast compile for development time
|
||||||
const compileClientTask = task.define('compile-client', task.series(util.rimraf('out'), compilation.copyCodiconsTask, compilation.compileApiProposalNamesTask, compilation.compileTask('src', 'out', false)));
|
const compileClientTask = task.define('compile-client', task.series(util.rimraf('out'), compilation.copyCodiconsTask, compilation.compileApiProposalNamesTask, compilation.compileExtensionPointNamesTask, compilation.compileTask('src', 'out', false)));
|
||||||
gulp.task(compileClientTask);
|
gulp.task(compileClientTask);
|
||||||
|
|
||||||
const watchClientTask = useEsbuildTranspile
|
const watchClientTask = useEsbuildTranspile
|
||||||
? task.define('watch-client', task.parallel(compilation.watchTask('out', false, 'src', { noEmit: true }), compilation.watchApiProposalNamesTask, compilation.watchCodiconsTask))
|
? task.define('watch-client', task.parallel(compilation.watchTask('out', false, 'src', { noEmit: true }), compilation.watchApiProposalNamesTask, compilation.watchExtensionPointNamesTask, compilation.watchCodiconsTask))
|
||||||
: task.define('watch-client', task.series(util.rimraf('out'), task.parallel(compilation.watchTask('out', false), compilation.watchApiProposalNamesTask, compilation.watchCodiconsTask)));
|
: task.define('watch-client', task.series(util.rimraf('out'), task.parallel(compilation.watchTask('out', false), compilation.watchApiProposalNamesTask, compilation.watchExtensionPointNamesTask, compilation.watchCodiconsTask)));
|
||||||
gulp.task(watchClientTask);
|
gulp.task(watchClientTask);
|
||||||
|
|
||||||
// All
|
// All
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ import * as tsb from './tsb/index.ts';
|
|||||||
import sourcemaps from 'gulp-sourcemaps';
|
import sourcemaps from 'gulp-sourcemaps';
|
||||||
|
|
||||||
|
|
||||||
|
import { extractExtensionPointNamesFromFile } from './extractExtensionPoints.ts';
|
||||||
|
|
||||||
|
|
||||||
// --- gulp-tsb: compile and transpile --------------------------------
|
// --- gulp-tsb: compile and transpile --------------------------------
|
||||||
|
|
||||||
const reporter = createReporter();
|
const reporter = createReporter();
|
||||||
@@ -351,6 +354,49 @@ export const compileApiProposalNamesTask = task.define('compile-api-proposal-nam
|
|||||||
.pipe(apiProposalNamesReporter.end(true));
|
.pipe(apiProposalNamesReporter.end(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function generateExtensionPointNames() {
|
||||||
|
const collectedNames: string[] = [];
|
||||||
|
|
||||||
|
const input = es.through();
|
||||||
|
const output = input
|
||||||
|
.pipe(es.through(function (file: File) {
|
||||||
|
const contents = file.contents?.toString('utf-8');
|
||||||
|
if (contents && contents.includes('registerExtensionPoint')) {
|
||||||
|
const sourceFile = ts.createSourceFile(file.path, contents, ts.ScriptTarget.Latest, true);
|
||||||
|
collectedNames.push(...extractExtensionPointNamesFromFile(sourceFile));
|
||||||
|
}
|
||||||
|
}, function () {
|
||||||
|
collectedNames.sort();
|
||||||
|
const content = JSON.stringify(collectedNames, undefined, '\t') + '\n';
|
||||||
|
this.emit('data', new File({
|
||||||
|
path: 'vs/workbench/services/extensions/common/extensionPoints.json',
|
||||||
|
contents: Buffer.from(content)
|
||||||
|
}));
|
||||||
|
this.emit('end');
|
||||||
|
}));
|
||||||
|
|
||||||
|
return es.duplex(input, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
const extensionPointNamesReporter = createReporter('extension-point-names');
|
||||||
|
|
||||||
|
export const compileExtensionPointNamesTask = task.define('compile-extension-point-names', () => {
|
||||||
|
return gulp.src('src/vs/workbench/**/*.ts')
|
||||||
|
.pipe(generateExtensionPointNames())
|
||||||
|
.pipe(gulp.dest('src'))
|
||||||
|
.pipe(extensionPointNamesReporter.end(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
export const watchExtensionPointNamesTask = task.define('watch-extension-point-names', () => {
|
||||||
|
const task = () => gulp.src('src/vs/workbench/**/*.ts')
|
||||||
|
.pipe(generateExtensionPointNames())
|
||||||
|
.pipe(extensionPointNamesReporter.end(true));
|
||||||
|
|
||||||
|
return watch('src/vs/workbench/**/*.ts', { readDelay: 200 })
|
||||||
|
.pipe(util.debounce(task))
|
||||||
|
.pipe(gulp.dest('src'));
|
||||||
|
});
|
||||||
|
|
||||||
export const watchApiProposalNamesTask = task.define('watch-api-proposal-names', () => {
|
export const watchApiProposalNamesTask = task.define('watch-api-proposal-names', () => {
|
||||||
const task = () => gulp.src('src/vscode-dts/**')
|
const task = () => gulp.src('src/vscode-dts/**')
|
||||||
.pipe(generateApiProposalNames())
|
.pipe(generateApiProposalNames())
|
||||||
|
|||||||
224
build/lib/extractExtensionPoints.ts
Normal file
224
build/lib/extractExtensionPoints.ts
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts extension point names from TypeScript source files by parsing the AST
|
||||||
|
* to find all calls to `ExtensionsRegistry.registerExtensionPoint(...)`.
|
||||||
|
*
|
||||||
|
* Handles:
|
||||||
|
* - Inline string literals: `{ extensionPoint: 'foo' }`
|
||||||
|
* - Enum member values passed via function parameters
|
||||||
|
* - Imported descriptor variables where the `extensionPoint` property is in another file
|
||||||
|
*
|
||||||
|
* This module can be used standalone (`node build/lib/extractExtensionPoints.ts`)
|
||||||
|
* to regenerate the extension points file, or imported for use in gulp build tasks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ts from 'typescript';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract extension point names registered via `registerExtensionPoint` from
|
||||||
|
* a single TypeScript source file's AST. No type checker is needed.
|
||||||
|
*/
|
||||||
|
export function extractExtensionPointNamesFromFile(sourceFile: ts.SourceFile): string[] {
|
||||||
|
const results: string[] = [];
|
||||||
|
visit(sourceFile);
|
||||||
|
return results;
|
||||||
|
|
||||||
|
function visit(node: ts.Node): void {
|
||||||
|
if (ts.isCallExpression(node)) {
|
||||||
|
const expr = node.expression;
|
||||||
|
if (ts.isPropertyAccessExpression(expr) && expr.name.text === 'registerExtensionPoint') {
|
||||||
|
handleRegisterCall(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ts.forEachChild(node, visit);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRegisterCall(call: ts.CallExpression): void {
|
||||||
|
const arg = call.arguments[0];
|
||||||
|
if (!arg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ts.isObjectLiteralExpression(arg)) {
|
||||||
|
handleInlineDescriptor(call, arg);
|
||||||
|
} else if (ts.isIdentifier(arg)) {
|
||||||
|
handleImportedDescriptor(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInlineDescriptor(call: ts.CallExpression, obj: ts.ObjectLiteralExpression): void {
|
||||||
|
const epProp = findExtensionPointProperty(obj);
|
||||||
|
if (!epProp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ts.isStringLiteral(epProp.initializer)) {
|
||||||
|
results.push(epProp.initializer.text);
|
||||||
|
} else if (ts.isIdentifier(epProp.initializer)) {
|
||||||
|
// The value references a function parameter - resolve via call sites
|
||||||
|
handleParameterReference(call, epProp.initializer.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleParameterReference(registerCall: ts.CallExpression, paramName: string): void {
|
||||||
|
// Walk up to find the containing function
|
||||||
|
let current: ts.Node | undefined = registerCall.parent;
|
||||||
|
while (current && !ts.isFunctionDeclaration(current) && !ts.isFunctionExpression(current) && !ts.isArrowFunction(current)) {
|
||||||
|
current = current.parent;
|
||||||
|
}
|
||||||
|
if (!current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fn = current as ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction;
|
||||||
|
|
||||||
|
// Find which parameter position matches paramName
|
||||||
|
const paramIndex = fn.parameters.findIndex(
|
||||||
|
p => ts.isIdentifier(p.name) && p.name.text === paramName
|
||||||
|
);
|
||||||
|
if (paramIndex < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the function name to locate call sites
|
||||||
|
const fnName = ts.isFunctionDeclaration(fn) && fn.name ? fn.name.text : undefined;
|
||||||
|
if (!fnName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all call sites of this function in the same file
|
||||||
|
ts.forEachChild(sourceFile, function findCalls(node) {
|
||||||
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === fnName) {
|
||||||
|
const callArg = node.arguments[paramIndex];
|
||||||
|
if (callArg) {
|
||||||
|
const value = resolveStringValue(callArg);
|
||||||
|
if (value) {
|
||||||
|
results.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ts.forEachChild(node, findCalls);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleImportedDescriptor(identifier: ts.Identifier): void {
|
||||||
|
const name = identifier.text;
|
||||||
|
for (const stmt of sourceFile.statements) {
|
||||||
|
if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!ts.isNamedImports(stmt.importClause.namedBindings)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const element of stmt.importClause.namedBindings.elements) {
|
||||||
|
if (element.name.text !== name || !ts.isStringLiteral(stmt.moduleSpecifier)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const modulePath = stmt.moduleSpecifier.text;
|
||||||
|
const resolvedPath = path.resolve(
|
||||||
|
path.dirname(sourceFile.fileName),
|
||||||
|
modulePath.replace(/\.js$/, '.ts')
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(resolvedPath, 'utf-8');
|
||||||
|
const importedFile = ts.createSourceFile(resolvedPath, content, ts.ScriptTarget.Latest, true);
|
||||||
|
const originalName = element.propertyName?.text || element.name.text;
|
||||||
|
const value = findExtensionPointInVariable(importedFile, originalName);
|
||||||
|
if (value) {
|
||||||
|
results.push(value);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Imported file not found, skip
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveStringValue(node: ts.Node): string | undefined {
|
||||||
|
if (ts.isStringLiteral(node)) {
|
||||||
|
return node.text;
|
||||||
|
}
|
||||||
|
// Property access: Enum.Member
|
||||||
|
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
|
||||||
|
const enumName = node.expression.text;
|
||||||
|
const memberName = node.name.text;
|
||||||
|
for (const stmt of sourceFile.statements) {
|
||||||
|
if (ts.isEnumDeclaration(stmt) && stmt.name.text === enumName) {
|
||||||
|
for (const member of stmt.members) {
|
||||||
|
if (ts.isIdentifier(member.name) && member.name.text === memberName
|
||||||
|
&& member.initializer && ts.isStringLiteral(member.initializer)) {
|
||||||
|
return member.initializer.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findExtensionPointProperty(obj: ts.ObjectLiteralExpression): ts.PropertyAssignment | undefined {
|
||||||
|
for (const prop of obj.properties) {
|
||||||
|
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'extensionPoint') {
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findExtensionPointInVariable(sourceFile: ts.SourceFile, varName: string): string | undefined {
|
||||||
|
for (const stmt of sourceFile.statements) {
|
||||||
|
if (!ts.isVariableStatement(stmt)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const decl of stmt.declarationList.declarations) {
|
||||||
|
if (ts.isIdentifier(decl.name) && decl.name.text === varName
|
||||||
|
&& decl.initializer && ts.isObjectLiteralExpression(decl.initializer)) {
|
||||||
|
const epProp = findExtensionPointProperty(decl.initializer);
|
||||||
|
if (epProp && ts.isStringLiteral(epProp.initializer)) {
|
||||||
|
return epProp.initializer.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Standalone CLI ---
|
||||||
|
|
||||||
|
const rootDir = path.resolve(import.meta.dirname, '..', '..');
|
||||||
|
const srcDir = path.join(rootDir, 'src');
|
||||||
|
const outputPath = path.join(srcDir, 'vs', 'workbench', 'services', 'extensions', 'common', 'extensionPoints.json');
|
||||||
|
|
||||||
|
function scanDirectory(dir: string): string[] {
|
||||||
|
const names: string[] = [];
|
||||||
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
const fullPath = path.join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
names.push(...scanDirectory(fullPath));
|
||||||
|
} else if (entry.name.endsWith('.ts')) {
|
||||||
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
||||||
|
if (content.includes('registerExtensionPoint')) {
|
||||||
|
const sourceFile = ts.createSourceFile(fullPath, content, ts.ScriptTarget.Latest, true);
|
||||||
|
names.push(...extractExtensionPointNamesFromFile(sourceFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main(): void {
|
||||||
|
const names = scanDirectory(path.join(srcDir, 'vs', 'workbench'));
|
||||||
|
names.sort();
|
||||||
|
const output = JSON.stringify(names, undefined, '\t') + '\n';
|
||||||
|
fs.writeFileSync(outputPath, output, 'utf-8');
|
||||||
|
console.log(`Wrote ${names.length} extension points to ${path.relative(rootDir, outputPath)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.main) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
[
|
||||||
|
"authentication",
|
||||||
|
"breakpoints",
|
||||||
|
"chatAgents",
|
||||||
|
"chatContext",
|
||||||
|
"chatInstructions",
|
||||||
|
"chatOutputRenderers",
|
||||||
|
"chatParticipants",
|
||||||
|
"chatPromptFiles",
|
||||||
|
"chatSessions",
|
||||||
|
"chatSkills",
|
||||||
|
"chatViewsWelcome",
|
||||||
|
"colors",
|
||||||
|
"commands",
|
||||||
|
"configuration",
|
||||||
|
"configurationDefaults",
|
||||||
|
"continueEditSession",
|
||||||
|
"css",
|
||||||
|
"customEditors",
|
||||||
|
"debugVisualizers",
|
||||||
|
"debuggers",
|
||||||
|
"grammars",
|
||||||
|
"iconThemes",
|
||||||
|
"icons",
|
||||||
|
"jsonValidation",
|
||||||
|
"keybindings",
|
||||||
|
"languageModelChatProviders",
|
||||||
|
"languageModelToolSets",
|
||||||
|
"languageModelTools",
|
||||||
|
"languages",
|
||||||
|
"localizations",
|
||||||
|
"mcpServerDefinitionProviders",
|
||||||
|
"menus",
|
||||||
|
"notebookPreload",
|
||||||
|
"notebookRenderer",
|
||||||
|
"notebooks",
|
||||||
|
"problemMatchers",
|
||||||
|
"problemPatterns",
|
||||||
|
"productIconThemes",
|
||||||
|
"remoteCodingAgents",
|
||||||
|
"remoteHelp",
|
||||||
|
"resourceLabelFormatters",
|
||||||
|
"semanticTokenModifiers",
|
||||||
|
"semanticTokenScopes",
|
||||||
|
"semanticTokenTypes",
|
||||||
|
"snippets",
|
||||||
|
"speechProviders",
|
||||||
|
"statusBarItems",
|
||||||
|
"submenus",
|
||||||
|
"taskDefinitions",
|
||||||
|
"terminal",
|
||||||
|
"terminalQuickFixes",
|
||||||
|
"themes",
|
||||||
|
"views",
|
||||||
|
"viewsContainers",
|
||||||
|
"viewsWelcome",
|
||||||
|
"walkthroughs"
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user