diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index ff18694436d..029235f7709 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -490,54 +490,56 @@ function markNodes(ts, languageService, options) { } const nodeSourceFile = node.getSourceFile(); const loop = (node) => { - const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node); - if (symbolImportNode) { - setColor(symbolImportNode, 2 /* NodeColor.Black */); - const importDeclarationNode = findParentImportDeclaration(symbolImportNode); - if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { - enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + const symbols = getRealNodeSymbol(ts, checker, node); + for (const { symbol, symbolImportNode } of symbols) { + if (symbolImportNode) { + setColor(symbolImportNode, 2 /* NodeColor.Black */); + const importDeclarationNode = findParentImportDeclaration(symbolImportNode); + if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { + enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + } } - } - if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { - for (let i = 0, len = symbol.declarations.length; i < len; i++) { - const declaration = symbol.declarations[i]; - if (ts.isSourceFile(declaration)) { - // Do not enqueue full source files - // (they can be the declaration of a module import) - continue; - } - if (options.shakeLevel === 2 /* ShakeLevel.ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { - enqueue_black(declaration.name); - for (let j = 0; j < declaration.members.length; j++) { - const member = declaration.members[j]; - const memberName = member.name ? member.name.getText() : null; - if (ts.isConstructorDeclaration(member) - || ts.isConstructSignatureDeclaration(member) - || ts.isIndexSignatureDeclaration(member) - || ts.isCallSignatureDeclaration(member) - || memberName === '[Symbol.iterator]' - || memberName === '[Symbol.toStringTag]' - || memberName === 'toJSON' - || memberName === 'toString' - || memberName === 'dispose' // TODO: keeping all `dispose` methods - || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... - ) { - enqueue_black(member); + if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { + for (let i = 0, len = symbol.declarations.length; i < len; i++) { + const declaration = symbol.declarations[i]; + if (ts.isSourceFile(declaration)) { + // Do not enqueue full source files + // (they can be the declaration of a module import) + continue; + } + if (options.shakeLevel === 2 /* ShakeLevel.ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { + enqueue_black(declaration.name); + for (let j = 0; j < declaration.members.length; j++) { + const member = declaration.members[j]; + const memberName = member.name ? member.name.getText() : null; + if (ts.isConstructorDeclaration(member) + || ts.isConstructSignatureDeclaration(member) + || ts.isIndexSignatureDeclaration(member) + || ts.isCallSignatureDeclaration(member) + || memberName === '[Symbol.iterator]' + || memberName === '[Symbol.toStringTag]' + || memberName === 'toJSON' + || memberName === 'toString' + || memberName === 'dispose' // TODO: keeping all `dispose` methods + || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... + ) { + enqueue_black(member); + } + if (isStaticMemberWithSideEffects(ts, member)) { + enqueue_black(member); + } } - if (isStaticMemberWithSideEffects(ts, member)) { - enqueue_black(member); + // queue the heritage clauses + if (declaration.heritageClauses) { + for (const heritageClause of declaration.heritageClauses) { + enqueue_black(heritageClause); + } } } - // queue the heritage clauses - if (declaration.heritageClauses) { - for (const heritageClause of declaration.heritageClauses) { - enqueue_black(heritageClause); - } + else { + enqueue_black(declaration); } } - else { - enqueue_black(declaration); - } } } node.forEachChild(loop); @@ -736,13 +738,20 @@ function findSymbolFromHeritageType(ts, checker, type) { return findSymbolFromHeritageType(ts, checker, type.expression); } if (ts.isIdentifier(type)) { - return getRealNodeSymbol(ts, checker, type)[0]; + const tmp = getRealNodeSymbol(ts, checker, type); + return (tmp.length > 0 ? tmp[0].symbol : null); } if (ts.isPropertyAccessExpression(type)) { return findSymbolFromHeritageType(ts, checker, type.name); } return null; } +class SymbolImportTuple { + constructor(symbol, symbolImportNode) { + this.symbol = symbol; + this.symbolImportNode = symbolImportNode; + } +} /** * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) */ @@ -774,7 +783,7 @@ function getRealNodeSymbol(ts, checker, node) { } if (!ts.isShorthandPropertyAssignment(node)) { if (node.getChildCount() !== 0) { - return [null, null]; + return []; } } const { parent } = node; @@ -820,10 +829,7 @@ function getRealNodeSymbol(ts, checker, node) { const type = checker.getTypeAtLocation(parent.parent); if (name && type) { if (type.isUnion()) { - const prop = type.types[0].getProperty(name); - if (prop) { - symbol = prop; - } + return generateMultipleSymbols(type, name, importNode); } else { const prop = type.getProperty(name); @@ -854,9 +860,19 @@ function getRealNodeSymbol(ts, checker, node) { } } if (symbol && symbol.declarations) { - return [symbol, importNode]; + return [new SymbolImportTuple(symbol, importNode)]; + } + return []; + function generateMultipleSymbols(type, name, importNode) { + const result = []; + for (const t of type.types) { + const prop = t.getProperty(name); + if (prop && prop.declarations) { + result.push(new SymbolImportTuple(prop, importNode)); + } + } + return result; } - return [null, null]; } /** Get the token whose text contains the position */ function getTokenAtPosition(ts, sourceFile, position, allowPositionInLeadingTrivia, includeEndPosition) { diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index ef829426d94..020e567eb72 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -609,58 +609,60 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language const nodeSourceFile = node.getSourceFile(); const loop = (node: ts.Node) => { - const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node); - if (symbolImportNode) { - setColor(symbolImportNode, NodeColor.Black); - const importDeclarationNode = findParentImportDeclaration(symbolImportNode); - if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { - enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); - } - } - - if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { - for (let i = 0, len = symbol.declarations.length; i < len; i++) { - const declaration = symbol.declarations[i]; - if (ts.isSourceFile(declaration)) { - // Do not enqueue full source files - // (they can be the declaration of a module import) - continue; + const symbols = getRealNodeSymbol(ts, checker, node); + for (const { symbol, symbolImportNode } of symbols) { + if (symbolImportNode) { + setColor(symbolImportNode, NodeColor.Black); + const importDeclarationNode = findParentImportDeclaration(symbolImportNode); + if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { + enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); } + } - if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { - enqueue_black(declaration.name!); - - for (let j = 0; j < declaration.members.length; j++) { - const member = declaration.members[j]; - const memberName = member.name ? member.name.getText() : null; - if ( - ts.isConstructorDeclaration(member) - || ts.isConstructSignatureDeclaration(member) - || ts.isIndexSignatureDeclaration(member) - || ts.isCallSignatureDeclaration(member) - || memberName === '[Symbol.iterator]' - || memberName === '[Symbol.toStringTag]' - || memberName === 'toJSON' - || memberName === 'toString' - || memberName === 'dispose'// TODO: keeping all `dispose` methods - || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... - ) { - enqueue_black(member); - } - - if (isStaticMemberWithSideEffects(ts, member)) { - enqueue_black(member); - } + if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { + for (let i = 0, len = symbol.declarations.length; i < len; i++) { + const declaration = symbol.declarations[i]; + if (ts.isSourceFile(declaration)) { + // Do not enqueue full source files + // (they can be the declaration of a module import) + continue; } - // queue the heritage clauses - if (declaration.heritageClauses) { - for (const heritageClause of declaration.heritageClauses) { - enqueue_black(heritageClause); + if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { + enqueue_black(declaration.name!); + + for (let j = 0; j < declaration.members.length; j++) { + const member = declaration.members[j]; + const memberName = member.name ? member.name.getText() : null; + if ( + ts.isConstructorDeclaration(member) + || ts.isConstructSignatureDeclaration(member) + || ts.isIndexSignatureDeclaration(member) + || ts.isCallSignatureDeclaration(member) + || memberName === '[Symbol.iterator]' + || memberName === '[Symbol.toStringTag]' + || memberName === 'toJSON' + || memberName === 'toString' + || memberName === 'dispose'// TODO: keeping all `dispose` methods + || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... + ) { + enqueue_black(member); + } + + if (isStaticMemberWithSideEffects(ts, member)) { + enqueue_black(member); + } } + + // queue the heritage clauses + if (declaration.heritageClauses) { + for (const heritageClause of declaration.heritageClauses) { + enqueue_black(heritageClause); + } + } + } else { + enqueue_black(declaration); } - } else { - enqueue_black(declaration); } } } @@ -879,7 +881,8 @@ function findSymbolFromHeritageType(ts: typeof import('typescript'), checker: ts return findSymbolFromHeritageType(ts, checker, type.expression); } if (ts.isIdentifier(type)) { - return getRealNodeSymbol(ts, checker, type)[0]; + const tmp = getRealNodeSymbol(ts, checker, type); + return (tmp.length > 0 ? tmp[0].symbol : null); } if (ts.isPropertyAccessExpression(type)) { return findSymbolFromHeritageType(ts, checker, type.name); @@ -887,10 +890,17 @@ function findSymbolFromHeritageType(ts: typeof import('typescript'), checker: ts return null; } +class SymbolImportTuple { + constructor( + public readonly symbol: ts.Symbol | null, + public readonly symbolImportNode: ts.Declaration | null + ) { } +} + /** * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) */ -function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChecker, node: ts.Node): [ts.Symbol | null, ts.Declaration | null] { +function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChecker, node: ts.Node): SymbolImportTuple[] { // Use some TypeScript internals to avoid code duplication type ObjectLiteralElementWithName = ts.ObjectLiteralElement & { name: ts.PropertyName; parent: ts.ObjectLiteralExpression | ts.JsxAttributes }; @@ -923,7 +933,7 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec if (!ts.isShorthandPropertyAssignment(node)) { if (node.getChildCount() !== 0) { - return [null, null]; + return []; } } @@ -976,10 +986,7 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec const type = checker.getTypeAtLocation(parent.parent); if (name && type) { if (type.isUnion()) { - const prop = type.types[0].getProperty(name); - if (prop) { - symbol = prop; - } + return generateMultipleSymbols(type, name, importNode); } else { const prop = type.getProperty(name); if (prop) { @@ -1011,10 +1018,21 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec } if (symbol && symbol.declarations) { - return [symbol, importNode]; + return [new SymbolImportTuple(symbol, importNode)]; } - return [null, null]; + return []; + + function generateMultipleSymbols(type: ts.UnionType, name: string, importNode: ts.Declaration | null): SymbolImportTuple[] { + const result: SymbolImportTuple[] = []; + for (const t of type.types) { + const prop = t.getProperty(name); + if (prop && prop.declarations) { + result.push(new SymbolImportTuple(prop, importNode)); + } + } + return result; + } } /** Get the token whose text contains the position */ diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 7ea373d798a..c09a7e422a3 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -540,13 +540,7 @@ export class SyncActionDescriptor { type OneOrN = T | T[]; -export interface IAction2Options extends ICommandAction { - - /** - * Shorthand to add this command to the command palette - */ - f1?: boolean; - +interface IAction2CommonOptions extends ICommandAction { /** * One or many menu items. */ @@ -571,6 +565,41 @@ export interface IAction2Options extends ICommandAction { _isFakeAction?: true; } +interface IBaseAction2Options extends IAction2CommonOptions { + + /** + * This type is used when an action is not going to show up in the command palette. + * In that case, it's able to use a string for the `title` and `category` properties. + */ + f1?: false; +} + +interface ICommandPaletteOptions extends IAction2CommonOptions { + + /** + * The title of the command that will be displayed in the command palette after the category. + * This overrides {@link ICommandAction.title} to ensure a string isn't used so that the title + * includes the localized value and the original value for users using language packs. + */ + title: ICommandActionTitle; + + /** + * The category of the command that will be displayed in the command palette before the title suffixed. + * with a colon This overrides {@link ICommandAction.title} to ensure a string isn't used so that + * the title includes the localized value and the original value for users using language packs. + */ + category?: keyof typeof Categories | ILocalizedString; + + /** + * Shorthand to add this command to the command palette. Note: this is not the only way to declare that + * a command should be in the command palette... however, enforcing ILocalizedString in the other scenarios + * is much more challenging and this gets us most of the way there. + */ + f1: true; +} + +export type IAction2Options = ICommandPaletteOptions | IBaseAction2Options; + export interface IAction2F1RequiredOptions { title: ICommandActionTitle; category?: keyof typeof Categories | ILocalizedString; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 4bbf610ea74..5571c168904 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -448,10 +448,10 @@ async function runAction(action: IAction): Promise { } } -interface IExtensionActionOptions extends IAction2Options { +type IExtensionActionOptions = IAction2Options & { menuTitles?: { [id: string]: string }; run(accessor: ServicesAccessor, ...args: any[]): Promise; -} +}; class ExtensionsContributions extends Disposable implements IWorkbenchContribution { @@ -994,9 +994,8 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi title: { value: localize('extensionUpdates', "Show Extension Updates"), original: 'Show Extension Updates' }, category: ExtensionsLocalizedLabel, precondition: CONTEXT_HAS_GALLERY, + f1: true, menu: [{ - id: MenuId.CommandPalette, - }, { id: extensionsFilterSubMenu, group: '3_installed', when: CONTEXT_HAS_GALLERY, @@ -1049,7 +1048,6 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: 'workbench.extensions.action.showDisabledExtensions', title: { value: localize('showDisabledExtensions', "Show Disabled Extensions"), original: 'Show Disabled Extensions' }, category: ExtensionsLocalizedLabel, - menu: [{ id: MenuId.CommandPalette, when: ContextKeyExpr.or(CONTEXT_HAS_LOCAL_SERVER, CONTEXT_HAS_REMOTE_SERVER, CONTEXT_HAS_WEB_SERVER) diff --git a/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts b/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts index 46f62be9e1c..cf919a8c50b 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts @@ -7,12 +7,12 @@ import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, IAction2Options } from 'vs/platform/actions/common/actions'; -const defaultOptions: Partial = { +const defaultOptions = { category: { value: localize('snippets', 'Snippets'), original: 'Snippets' }, -}; +} as const; export abstract class SnippetsAction extends Action2 { diff --git a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts index 3bc1b0b6b89..baee9823a1f 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -214,8 +214,8 @@ export class ConfigureSnippets extends SnippetsAction { mnemonicTitle: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), original: 'User Snippets' }, + f1: true, menu: [ - { id: MenuId.CommandPalette }, { id: MenuId.MenubarPreferencesMenu, group: '3_snippets', order: 1 }, { id: MenuId.GlobalActivity, group: '3_snippets', order: 1 }, ] diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 6540fbab441..3dd0ba3046a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -2234,7 +2234,7 @@ export function registerTerminalActions() { id: TerminalCommandId.ShowTextureAtlas, title: { value: localize('workbench.action.terminal.showTextureAtlas', "Show Terminal Texture Atlas"), original: 'Show Terminal Texture Atlas' }, f1: true, - category: Categories.Developer.value, + category: Categories.Developer, precondition: ContextKeyExpr.or(TerminalContextKeys.isOpen) }); } diff --git a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts index 62c6d2c84fa..aa3fcee7718 100644 --- a/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts +++ b/src/vs/workbench/services/preferences/test/browser/keybindingsEditorModel.test.ts @@ -672,7 +672,7 @@ suite('KeybindingsEditorModel', () => { constructor() { super({ id: command, - title, + title: { value: title, original: title }, f1: true }); }