Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/lib/extractExtensionPoints.ts
13379 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
/**
7
* Extracts extension point names from TypeScript source files by parsing the AST
8
* to find all calls to `ExtensionsRegistry.registerExtensionPoint(...)`.
9
*
10
* Handles:
11
* - Inline string literals: `{ extensionPoint: 'foo' }`
12
* - Enum member values passed via function parameters
13
* - Imported descriptor variables where the `extensionPoint` property is in another file
14
*
15
* This module can be used standalone (`node build/lib/extractExtensionPoints.ts`)
16
* to regenerate the extension points file, or imported for use in gulp build tasks.
17
*/
18
19
import ts from 'typescript';
20
import path from 'path';
21
import fs from 'fs';
22
23
/**
24
* Extract extension point names registered via `registerExtensionPoint` from
25
* a single TypeScript source file's AST. No type checker is needed.
26
*/
27
export function extractExtensionPointNamesFromFile(sourceFile: ts.SourceFile): string[] {
28
const results: string[] = [];
29
visit(sourceFile);
30
return results;
31
32
function visit(node: ts.Node): void {
33
if (ts.isCallExpression(node)) {
34
const expr = node.expression;
35
if (ts.isPropertyAccessExpression(expr) && expr.name.text === 'registerExtensionPoint') {
36
handleRegisterCall(node);
37
}
38
}
39
ts.forEachChild(node, visit);
40
}
41
42
function handleRegisterCall(call: ts.CallExpression): void {
43
const arg = call.arguments[0];
44
if (!arg) {
45
return;
46
}
47
if (ts.isObjectLiteralExpression(arg)) {
48
handleInlineDescriptor(call, arg);
49
} else if (ts.isIdentifier(arg)) {
50
handleImportedDescriptor(arg);
51
}
52
}
53
54
function handleInlineDescriptor(call: ts.CallExpression, obj: ts.ObjectLiteralExpression): void {
55
const epProp = findExtensionPointProperty(obj);
56
if (!epProp) {
57
return;
58
}
59
if (ts.isStringLiteral(epProp.initializer)) {
60
results.push(epProp.initializer.text);
61
} else if (ts.isIdentifier(epProp.initializer)) {
62
// The value references a function parameter - resolve via call sites
63
handleParameterReference(call, epProp.initializer.text);
64
}
65
}
66
67
function handleParameterReference(registerCall: ts.CallExpression, paramName: string): void {
68
// Walk up to find the containing function
69
let current: ts.Node | undefined = registerCall.parent;
70
while (current && !ts.isFunctionDeclaration(current) && !ts.isFunctionExpression(current) && !ts.isArrowFunction(current)) {
71
current = current.parent;
72
}
73
if (!current) {
74
return;
75
}
76
const fn = current as ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction;
77
78
// Find which parameter position matches paramName
79
const paramIndex = fn.parameters.findIndex(
80
p => ts.isIdentifier(p.name) && p.name.text === paramName
81
);
82
if (paramIndex < 0) {
83
return;
84
}
85
86
// Find the function name to locate call sites
87
const fnName = ts.isFunctionDeclaration(fn) && fn.name ? fn.name.text : undefined;
88
if (!fnName) {
89
return;
90
}
91
92
// Find all call sites of this function in the same file
93
ts.forEachChild(sourceFile, function findCalls(node) {
94
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === fnName) {
95
const callArg = node.arguments[paramIndex];
96
if (callArg) {
97
const value = resolveStringValue(callArg);
98
if (value) {
99
results.push(value);
100
}
101
}
102
}
103
ts.forEachChild(node, findCalls);
104
});
105
}
106
107
function handleImportedDescriptor(identifier: ts.Identifier): void {
108
const name = identifier.text;
109
for (const stmt of sourceFile.statements) {
110
if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings) {
111
continue;
112
}
113
if (!ts.isNamedImports(stmt.importClause.namedBindings)) {
114
continue;
115
}
116
for (const element of stmt.importClause.namedBindings.elements) {
117
if (element.name.text !== name || !ts.isStringLiteral(stmt.moduleSpecifier)) {
118
continue;
119
}
120
const modulePath = stmt.moduleSpecifier.text;
121
const resolvedPath = path.resolve(
122
path.dirname(sourceFile.fileName),
123
modulePath.replace(/\.js$/, '.ts')
124
);
125
try {
126
const content = fs.readFileSync(resolvedPath, 'utf-8');
127
const importedFile = ts.createSourceFile(resolvedPath, content, ts.ScriptTarget.Latest, true);
128
const originalName = element.propertyName?.text || element.name.text;
129
const value = findExtensionPointInVariable(importedFile, originalName);
130
if (value) {
131
results.push(value);
132
}
133
} catch {
134
// Imported file not found, skip
135
}
136
return;
137
}
138
}
139
}
140
141
function resolveStringValue(node: ts.Node): string | undefined {
142
if (ts.isStringLiteral(node)) {
143
return node.text;
144
}
145
// Property access: Enum.Member
146
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
147
const enumName = node.expression.text;
148
const memberName = node.name.text;
149
for (const stmt of sourceFile.statements) {
150
if (ts.isEnumDeclaration(stmt) && stmt.name.text === enumName) {
151
for (const member of stmt.members) {
152
if (ts.isIdentifier(member.name) && member.name.text === memberName
153
&& member.initializer && ts.isStringLiteral(member.initializer)) {
154
return member.initializer.text;
155
}
156
}
157
}
158
}
159
}
160
return undefined;
161
}
162
}
163
164
function findExtensionPointProperty(obj: ts.ObjectLiteralExpression): ts.PropertyAssignment | undefined {
165
for (const prop of obj.properties) {
166
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'extensionPoint') {
167
return prop;
168
}
169
}
170
return undefined;
171
}
172
173
function findExtensionPointInVariable(sourceFile: ts.SourceFile, varName: string): string | undefined {
174
for (const stmt of sourceFile.statements) {
175
if (!ts.isVariableStatement(stmt)) {
176
continue;
177
}
178
for (const decl of stmt.declarationList.declarations) {
179
if (ts.isIdentifier(decl.name) && decl.name.text === varName
180
&& decl.initializer && ts.isObjectLiteralExpression(decl.initializer)) {
181
const epProp = findExtensionPointProperty(decl.initializer);
182
if (epProp && ts.isStringLiteral(epProp.initializer)) {
183
return epProp.initializer.text;
184
}
185
}
186
}
187
}
188
return undefined;
189
}
190
191
// --- Standalone CLI ---
192
193
const rootDir = path.resolve(import.meta.dirname, '..', '..');
194
const srcDir = path.join(rootDir, 'src');
195
const outputPath = path.join(srcDir, 'vs', 'workbench', 'services', 'extensions', 'common', 'extensionPoints.json');
196
197
function scanDirectory(dir: string): string[] {
198
const names: string[] = [];
199
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
200
const fullPath = path.join(dir, entry.name);
201
if (entry.isDirectory()) {
202
names.push(...scanDirectory(fullPath));
203
} else if (entry.name.endsWith('.ts')) {
204
const content = fs.readFileSync(fullPath, 'utf-8');
205
if (content.includes('registerExtensionPoint')) {
206
const sourceFile = ts.createSourceFile(fullPath, content, ts.ScriptTarget.Latest, true);
207
names.push(...extractExtensionPointNamesFromFile(sourceFile));
208
}
209
}
210
}
211
return names;
212
}
213
214
function normalize(s: string): string {
215
return s.replace(/\r\n/g, '\n');
216
}
217
218
function main(): void {
219
const names = scanDirectory(path.join(srcDir, 'vs', 'workbench'));
220
names.sort();
221
const output = JSON.stringify(names, undefined, '\t') + '\n';
222
try {
223
const existing = fs.readFileSync(outputPath, 'utf-8');
224
if (normalize(existing) === normalize(output)) {
225
console.log(`No changes to ${path.relative(rootDir, outputPath)}`);
226
return;
227
}
228
} catch {
229
// File doesn't exist yet, write it
230
}
231
fs.writeFileSync(outputPath, output, 'utf-8');
232
console.log(`Wrote ${names.length} extension points to ${path.relative(rootDir, outputPath)}`);
233
}
234
235
if (import.meta.main) {
236
main();
237
}
238
239