Mangle exported symbols (#182935)

* Mangle exported functions

For #180461

This prototype tries to mangle exported functions, saving a further 440kb from the bundle size

* Fix missing call

* Also try mangling top level exported consts too

* Fixing errors

* Don't run on build files

* Skip a few more manglings and revert change to namespace

* Skip a few more monaco files

* Also mangle consts that shadow types

This increases savings up to 3325

* Also mangle exported classes

* Skip mangling more localization functions for now

* Opt out pfs

* Update build script

* Run find locations task in parallel

This should speed up compile

* Cleanup before close

* Limit workers to avoid hitting memory limit

* Limit pool size

* Skip one more mangling

* Exclude entrypoints from mangling

* Try to fix web build and clean up code

* Exempt a few more projects

* Exempt another file

* Also exempt html

* Skip mangling ext entrypoints

* Use prefix that can't be confused with rpc calls

* Fix max call stack error

* Switch prefixes

* Don't mangle ambient declarations

* Use correct way of checking modifier flags

* Workaround getCombinedModifierFlags not doing what I'd expect

Maybe needs the checker to be enabled too? Just check parent chain instead for now

* Clean up code and add logic showing how enum mangling could work

* Remove a few more skipMangles

Use entrypoints instead

* Fix entrypoint name
This commit is contained in:
Matt Bierner 2023-06-12 23:18:05 -07:00 committed by GitHub
parent 79c4092768
commit debcf16fcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1338 additions and 134 deletions

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ import * as os from 'os';
import ts = require('typescript');
import * as File from 'vinyl';
import * as task from './task';
import { Mangler } from './mangleTypeScript';
import { Mangler } from './mangle/index';
import { RawSourceMap } from 'source-map';
const watch = require('./watch');
@ -124,21 +124,22 @@ export function compileTask(src: string, out: string, build: boolean, options: {
// mangle: TypeScript to TypeScript
let mangleStream = es.through();
if (build && !options.disableMangle) {
let ts2tsMangler = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data));
let ts2tsMangler = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data), { mangleExports: true, manglePrivateFields: true });
const newContentsByFileName = ts2tsMangler.computeNewFileContents(new Set(['saveState']));
mangleStream = es.through(function write(data: File & { sourceMap?: RawSourceMap }) {
mangleStream = es.through(async function write(data: File & { sourceMap?: RawSourceMap }) {
type TypeScriptExt = typeof ts & { normalizePath(path: string): string };
const tsNormalPath = (<TypeScriptExt>ts).normalizePath(data.path);
const newContents = newContentsByFileName.get(tsNormalPath);
const newContents = (await newContentsByFileName).get(tsNormalPath);
if (newContents !== undefined) {
data.contents = Buffer.from(newContents.out);
data.sourceMap = newContents.sourceMap && JSON.parse(newContents.sourceMap);
}
this.push(data);
}, function end() {
this.push(null);
}, async function end() {
// free resources
newContentsByFileName.clear();
(await newContentsByFileName).clear();
this.push(null);
(<any>ts2tsMangler) = undefined;
});
}

665
build/lib/mangle/index.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -3,12 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as path from 'path';
import * as fs from 'fs';
import * as path from 'path';
import { argv } from 'process';
import { Mapping, SourceMapGenerator } from 'source-map';
import * as ts from 'typescript';
import { pathToFileURL } from 'url';
import * as workerpool from 'workerpool';
import { StaticLanguageServiceHost } from './staticLanguageServiceHost';
const buildfile = require('../../../src/buildfile');
class ShortIdent {
@ -17,21 +20,20 @@ class ShortIdent {
'import', 'in', 'instanceof', 'let', 'new', 'null', 'return', 'static', 'super', 'switch', 'this', 'throw',
'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield']);
private static _alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
private static _alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890$_'.split('');
private _value = 0;
private readonly _isNameTaken: (name: string) => boolean;
constructor(isNameTaken: (name: string) => boolean) {
this._isNameTaken = name => ShortIdent._keywords.has(name) || isNameTaken(name);
}
constructor(
private readonly prefix: string
) { }
next(): string {
const candidate = ShortIdent.convert(this._value);
next(isNameTaken?: (name: string) => boolean): string {
const candidate = this.prefix + ShortIdent.convert(this._value);
this._value++;
if (this._isNameTaken(candidate)) {
if (ShortIdent._keywords.has(candidate) || /^[_0-9]/.test(candidate) || isNameTaken?.(candidate)) {
// try again
return this.next();
return this.next(isNameTaken);
}
return candidate;
}
@ -181,8 +183,7 @@ class ClassData {
data.replacements = new Map();
const identPool = new ShortIdent(name => {
const isNameTaken = (name: string) => {
// locally taken
if (data._isNameTaken(name)) {
return true;
@ -212,11 +213,12 @@ class ClassData {
}
return false;
});
};
const identPool = new ShortIdent('');
for (const [name, info] of data.fields) {
if (ClassData._shouldMangle(info.type)) {
const shortName = identPool.next();
const shortName = identPool.next(isNameTaken);
data.replacements.set(name, shortName);
}
}
@ -237,12 +239,11 @@ class ClassData {
}
}
}
if ((<any>this.node.getSourceFile()).identifiers instanceof Map) {
// taken by any other usage
if ((<any>this.node.getSourceFile()).identifiers.has(name)) {
return true;
}
if (isNameTakenInFile(this.node, name)) {
return true;
}
return false;
}
@ -267,59 +268,122 @@ class ClassData {
}
}
class StaticLanguageServiceHost implements ts.LanguageServiceHost {
private readonly _cmdLine: ts.ParsedCommandLine;
private readonly _scriptSnapshots: Map<string, ts.IScriptSnapshot> = new Map();
constructor(readonly projectPath: string) {
const existingOptions: Partial<ts.CompilerOptions> = {};
const parsed = ts.readConfigFile(projectPath, ts.sys.readFile);
if (parsed.error) {
throw parsed.error;
}
this._cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, path.dirname(projectPath), existingOptions);
if (this._cmdLine.errors.length > 0) {
throw parsed.error;
function isNameTakenInFile(node: ts.Node, name: string): boolean {
const identifiers = (<any>node.getSourceFile()).identifiers;
if (identifiers instanceof Map) {
if (identifiers.has(name)) {
return true;
}
}
getCompilationSettings(): ts.CompilerOptions {
return this._cmdLine.options;
return false;
}
const fileIdents = new class {
private readonly idents = new ShortIdent('$');
next() {
return this.idents.next();
}
getScriptFileNames(): string[] {
return this._cmdLine.fileNames;
};
const skippedExportMangledFiles = [
// Build
'css.build',
'nls.build',
// Monaco
'editorCommon',
'editorOptions',
'editorZoom',
'standaloneEditor',
'standaloneEnums',
'standaloneLanguages',
// Generated
'extensionsApiProposals',
// Module passed around as type
'pfs',
// entry points
...[
buildfile.entrypoint('vs/server/node/server.main', []),
buildfile.entrypoint('vs/workbench/workbench.desktop.main', []),
buildfile.base,
buildfile.workerExtensionHost,
buildfile.workerNotebook,
buildfile.workerLanguageDetection,
buildfile.workerLocalFileSearch,
buildfile.workerProfileAnalysis,
buildfile.workbenchDesktop,
buildfile.workbenchWeb,
buildfile.code
].flat().map(x => x.name),
];
const skippedExportMangledProjects = [
// Test projects
'vscode-api-tests',
// These projects use webpack to dynamically rewrite imports, which messes up our mangling
'configuration-editing',
'microsoft-authentication',
'github-authentication',
'html-language-features/server',
];
const skippedExportMangledSymbols = [
// Don't mangle extension entry points
'activate',
'deactivate',
];
class DeclarationData {
readonly replacementName: string;
constructor(
readonly fileName: string,
readonly node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.EnumDeclaration | ts.VariableDeclaration,
private readonly service: ts.LanguageService,
) {
// Todo: generate replacement names based on usage count, with more used names getting shorter identifiers
this.replacementName = fileIdents.next();
}
getScriptVersion(_fileName: string): string {
return '1';
}
getProjectVersion(): string {
return '1';
}
getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined {
let result: ts.IScriptSnapshot | undefined = this._scriptSnapshots.get(fileName);
if (result === undefined) {
const content = ts.sys.readFile(fileName);
if (content === undefined) {
return undefined;
get locations(): Iterable<{ fileName: string; offset: number }> {
if (ts.isVariableDeclaration(this.node)) {
// If the const aliases any types, we need to rename those too
const definitionResult = this.service.getDefinitionAndBoundSpan(this.fileName, this.node.name.getStart());
if (definitionResult?.definitions && definitionResult.definitions.length > 1) {
return definitionResult.definitions.map(x => ({ fileName: x.fileName, offset: x.textSpan.start }));
}
result = ts.ScriptSnapshot.fromString(content);
this._scriptSnapshots.set(fileName, result);
}
return result;
return [{
fileName: this.fileName,
offset: this.node.name!.getStart()
}];
}
getCurrentDirectory(): string {
return path.dirname(this.projectPath);
shouldMangle(newName: string): boolean {
const currentName = this.node.name!.getText();
if (currentName.startsWith('$') || skippedExportMangledSymbols.includes(currentName)) {
return false;
}
// New name is longer the existing one :'(
if (newName.length >= currentName.length) {
return false;
}
// Don't mangle functions we've explicitly opted out
if (this.node.getFullText().includes('@skipMangle')) {
return false;
}
return true;
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
return ts.getDefaultLibFilePath(options);
}
directoryExists = ts.sys.directoryExists;
getDirectories = ts.sys.getDirectories;
fileExists = ts.sys.fileExists;
readFile = ts.sys.readFile;
readDirectory = ts.sys.readDirectory;
// this is necessary to make source references work.
realpath = ts.sys.realpath;
}
export interface MangleOutput {
@ -339,26 +403,82 @@ export interface MangleOutput {
export class Mangler {
private readonly allClassDataByKey = new Map<string, ClassData>();
private readonly allExportedSymbols = new Set<DeclarationData>();
private readonly service: ts.LanguageService;
private readonly renameWorkerPool: workerpool.WorkerPool;
constructor(readonly projectPath: string, readonly log: typeof console.log = () => { }) {
constructor(
private readonly projectPath: string,
private readonly log: typeof console.log = () => { },
private readonly config: { readonly manglePrivateFields: boolean; readonly mangleExports: boolean },
) {
this.service = ts.createLanguageService(new StaticLanguageServiceHost(projectPath));
this.renameWorkerPool = workerpool.pool(path.join(__dirname, 'renameWorker.js'), {
maxWorkers: 2,
minWorkers: 'max'
});
}
computeNewFileContents(strictImplicitPublicHandling?: Set<string>): Map<string, MangleOutput> {
async computeNewFileContents(strictImplicitPublicHandling?: Set<string>): Promise<Map<string, MangleOutput>> {
// STEP: find all classes and their field info
// STEP:
// - Find all classes and their field info.
// - Find exported symbols.
const visit = (node: ts.Node): void => {
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
const anchor = node.name ?? node;
const key = `${node.getSourceFile().fileName}|${anchor.getStart()}`;
if (this.allClassDataByKey.has(key)) {
throw new Error('DUPE?');
if (this.config.manglePrivateFields) {
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
const anchor = node.name ?? node;
const key = `${node.getSourceFile().fileName}|${anchor.getStart()}`;
if (this.allClassDataByKey.has(key)) {
throw new Error('DUPE?');
}
this.allClassDataByKey.set(key, new ClassData(node.getSourceFile().fileName, node));
}
this.allClassDataByKey.set(key, new ClassData(node.getSourceFile().fileName, node));
}
if (this.config.mangleExports) {
// Find exported classes, functions, and vars
if (
(
// Exported class
ts.isClassDeclaration(node)
&& hasModifier(node, ts.SyntaxKind.ExportKeyword)
&& node.name
) || (
// Exported function
ts.isFunctionDeclaration(node)
&& ts.isSourceFile(node.parent)
&& hasModifier(node, ts.SyntaxKind.ExportKeyword)
&& node.name && node.body // On named function and not on the overload
) || (
// Exported variable
ts.isVariableDeclaration(node)
&& hasModifier(node.parent.parent, ts.SyntaxKind.ExportKeyword) // Variable statement is exported
&& ts.isSourceFile(node.parent.parent.parent)
)
// Disabled for now because we need to figure out how to handle
// enums that are used in monaco or extHost interfaces.
/* || (
// Exported enum
ts.isEnumDeclaration(node)
&& ts.isSourceFile(node.parent)
&& hasModifier(node, ts.SyntaxKind.ExportKeyword)
&& !hasModifier(node, ts.SyntaxKind.ConstKeyword) // Don't bother mangling const enums because these are inlined
&& node.name
*/
) {
if (isInAmbientContext(node)) {
return;
}
this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, this.service));
}
}
ts.forEachChild(node, visit);
};
@ -367,7 +487,7 @@ export class Mangler {
ts.forEachChild(file, visit);
}
}
this.log(`Done collecting classes: ${this.allClassDataByKey.size}`);
this.log(`Done collecting. Classes: ${this.allClassDataByKey.size}. Exported symbols: ${this.allExportedSymbols.size}`);
// STEP: connect sub and super-types
@ -433,9 +553,11 @@ export class Mangler {
for (const data of this.allClassDataByKey.values()) {
ClassData.fillInReplacement(data);
}
this.log(`Done creating replacements`);
this.log(`Done creating class replacements`);
// STEP: prepare rename edits
this.log(`Starting prepare rename edits`);
type Edit = { newText: string; offset: number; length: number };
const editsByFile = new Map<string, Edit[]>();
@ -447,9 +569,24 @@ export class Mangler {
edits.push(edit);
}
};
const appendRename = (newText: string, loc: ts.RenameLocation) => {
appendEdit(loc.fileName, {
newText: (loc.prefixText || '') + newText + (loc.suffixText || ''),
offset: loc.textSpan.start,
length: loc.textSpan.length
});
};
type RenameFn = (projectName: string, fileName: string, pos: number) => ts.RenameLocation[];
const renameResults: Array<Promise<{ readonly newName: string; readonly locations: readonly ts.RenameLocation[] }>> = [];
const queueRename = (fileName: string, pos: number, newName: string) => {
renameResults.push(Promise.resolve(this.renameWorkerPool.exec<RenameFn>('findRenameLocations', [this.projectPath, fileName, pos]))
.then((locations) => ({ newName, locations })));
};
for (const data of this.allClassDataByKey.values()) {
if (hasModifier(data.node, ts.SyntaxKind.DeclareKeyword)) {
continue;
}
@ -469,18 +606,39 @@ export class Mangler {
parent = parent.parent;
}
const newText = data.lookupShortName(name);
const locations = this.service.findRenameLocations(data.fileName, info.pos, false, false, true) ?? [];
for (const loc of locations) {
appendEdit(loc.fileName, {
newText: (loc.prefixText || '') + newText + (loc.suffixText || ''),
offset: loc.textSpan.start,
length: loc.textSpan.length
});
}
const newName = data.lookupShortName(name);
queueRename(data.fileName, info.pos, newName);
}
}
for (const data of this.allExportedSymbols.values()) {
if (data.fileName.endsWith('.d.ts')
|| skippedExportMangledProjects.some(proj => data.fileName.includes(proj))
|| skippedExportMangledFiles.some(file => data.fileName.endsWith(file + '.ts'))
) {
continue;
}
if (!data.shouldMangle(data.replacementName)) {
continue;
}
const newText = data.replacementName;
for (const { fileName, offset } of data.locations) {
queueRename(fileName, offset, newText);
}
}
await Promise.all(renameResults).then((result) => {
for (const { newName, locations } of result) {
for (const loc of locations) {
appendRename(newName, loc);
}
}
});
await this.renameWorkerPool.terminate();
this.log(`Done preparing edits: ${editsByFile.size} files`);
// STEP: apply all rename edits (per file)
@ -579,17 +737,32 @@ function hasModifier(node: ts.Node, kind: ts.SyntaxKind) {
return Boolean(modifiers?.find(mode => mode.kind === kind));
}
function isInAmbientContext(node: ts.Node): boolean {
for (let p = node.parent; p; p = p.parent) {
if (ts.isModuleDeclaration(p)) {
return true;
}
}
return false;
}
function normalize(path: string): string {
return path.replace(/\\/g, '/');
}
async function _run() {
const projectPath = path.join(__dirname, '../../src/tsconfig.json');
const projectBase = path.dirname(projectPath);
const root = path.join(__dirname, '..', '..', '..');
const projectBase = path.join(root, 'src');
const projectPath = path.join(projectBase, 'tsconfig.json');
const newProjectBase = path.join(path.dirname(projectBase), path.basename(projectBase) + '2');
for await (const [fileName, contents] of new Mangler(projectPath, console.log).computeNewFileContents(new Set(['saveState']))) {
fs.cpSync(projectBase, newProjectBase, { recursive: true });
const mangler = new Mangler(projectPath, console.log, {
mangleExports: true,
manglePrivateFields: true,
});
for (const [fileName, contents] of await mangler.computeNewFileContents(new Set(['saveState']))) {
const newFilePath = path.join(newProjectBase, path.relative(projectBase, fileName));
await fs.promises.mkdir(path.dirname(newFilePath), { recursive: true });
await fs.promises.writeFile(newFilePath, contents.out);

View File

@ -0,0 +1,20 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const workerpool = require("workerpool");
const staticLanguageServiceHost_1 = require("./staticLanguageServiceHost");
let service; // = ts.createLanguageService(new StaticLanguageServiceHost(projectPath));
function findRenameLocations(projectPath, fileName, position) {
if (!service) {
service = ts.createLanguageService(new staticLanguageServiceHost_1.StaticLanguageServiceHost(projectPath));
}
return service.findRenameLocations(fileName, position, false, false, true) ?? [];
}
workerpool.worker({
findRenameLocations
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVuYW1lV29ya2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsicmVuYW1lV29ya2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7QUFFaEcsaUNBQWlDO0FBQ2pDLHlDQUF5QztBQUN6QywyRUFBd0U7QUFFeEUsSUFBSSxPQUF1QyxDQUFDLENBQUEsMEVBQTBFO0FBRXRILFNBQVMsbUJBQW1CLENBQzNCLFdBQW1CLEVBQ25CLFFBQWdCLEVBQ2hCLFFBQWdCO0lBRWhCLElBQUksQ0FBQyxPQUFPLEVBQUU7UUFDYixPQUFPLEdBQUcsRUFBRSxDQUFDLHFCQUFxQixDQUFDLElBQUkscURBQXlCLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztLQUMvRTtJQUVELE9BQU8sT0FBTyxDQUFDLG1CQUFtQixDQUFDLFFBQVEsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDbEYsQ0FBQztBQUVELFVBQVUsQ0FBQyxNQUFNLENBQUM7SUFDakIsbUJBQW1CO0NBQ25CLENBQUMsQ0FBQyJ9

View File

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as workerpool from 'workerpool';
import { StaticLanguageServiceHost } from './staticLanguageServiceHost';
let service: ts.LanguageService | undefined;// = ts.createLanguageService(new StaticLanguageServiceHost(projectPath));
function findRenameLocations(
projectPath: string,
fileName: string,
position: number,
): readonly ts.RenameLocation[] {
if (!service) {
service = ts.createLanguageService(new StaticLanguageServiceHost(projectPath));
}
return service.findRenameLocations(fileName, position, false, false, true) ?? [];
}
workerpool.worker({
findRenameLocations
});

View File

@ -0,0 +1,65 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.StaticLanguageServiceHost = void 0;
const ts = require("typescript");
const path = require("path");
class StaticLanguageServiceHost {
projectPath;
_cmdLine;
_scriptSnapshots = new Map();
constructor(projectPath) {
this.projectPath = projectPath;
const existingOptions = {};
const parsed = ts.readConfigFile(projectPath, ts.sys.readFile);
if (parsed.error) {
throw parsed.error;
}
this._cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, path.dirname(projectPath), existingOptions);
if (this._cmdLine.errors.length > 0) {
throw parsed.error;
}
}
getCompilationSettings() {
return this._cmdLine.options;
}
getScriptFileNames() {
return this._cmdLine.fileNames;
}
getScriptVersion(_fileName) {
return '1';
}
getProjectVersion() {
return '1';
}
getScriptSnapshot(fileName) {
let result = this._scriptSnapshots.get(fileName);
if (result === undefined) {
const content = ts.sys.readFile(fileName);
if (content === undefined) {
return undefined;
}
result = ts.ScriptSnapshot.fromString(content);
this._scriptSnapshots.set(fileName, result);
}
return result;
}
getCurrentDirectory() {
return path.dirname(this.projectPath);
}
getDefaultLibFileName(options) {
return ts.getDefaultLibFilePath(options);
}
directoryExists = ts.sys.directoryExists;
getDirectories = ts.sys.getDirectories;
fileExists = ts.sys.fileExists;
readFile = ts.sys.readFile;
readDirectory = ts.sys.readDirectory;
// this is necessary to make source references work.
realpath = ts.sys.realpath;
}
exports.StaticLanguageServiceHost = StaticLanguageServiceHost;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhdGljTGFuZ3VhZ2VTZXJ2aWNlSG9zdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInN0YXRpY0xhbmd1YWdlU2VydmljZUhvc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsaUNBQWlDO0FBQ2pDLDZCQUE2QjtBQUU3QixNQUFhLHlCQUF5QjtJQUtoQjtJQUhKLFFBQVEsQ0FBdUI7SUFDL0IsZ0JBQWdCLEdBQW9DLElBQUksR0FBRyxFQUFFLENBQUM7SUFFL0UsWUFBcUIsV0FBbUI7UUFBbkIsZ0JBQVcsR0FBWCxXQUFXLENBQVE7UUFDdkMsTUFBTSxlQUFlLEdBQWdDLEVBQUUsQ0FBQztRQUN4RCxNQUFNLE1BQU0sR0FBRyxFQUFFLENBQUMsY0FBYyxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQy9ELElBQUksTUFBTSxDQUFDLEtBQUssRUFBRTtZQUNqQixNQUFNLE1BQU0sQ0FBQyxLQUFLLENBQUM7U0FDbkI7UUFDRCxJQUFJLENBQUMsUUFBUSxHQUFHLEVBQUUsQ0FBQywwQkFBMEIsQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsRUFBRSxlQUFlLENBQUMsQ0FBQztRQUNqSCxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUU7WUFDcEMsTUFBTSxNQUFNLENBQUMsS0FBSyxDQUFDO1NBQ25CO0lBQ0YsQ0FBQztJQUNELHNCQUFzQjtRQUNyQixPQUFPLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDO0lBQzlCLENBQUM7SUFDRCxrQkFBa0I7UUFDakIsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQztJQUNoQyxDQUFDO0lBQ0QsZ0JBQWdCLENBQUMsU0FBaUI7UUFDakMsT0FBTyxHQUFHLENBQUM7SUFDWixDQUFDO0lBQ0QsaUJBQWlCO1FBQ2hCLE9BQU8sR0FBRyxDQUFDO0lBQ1osQ0FBQztJQUNELGlCQUFpQixDQUFDLFFBQWdCO1FBQ2pDLElBQUksTUFBTSxHQUFtQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ2pGLElBQUksTUFBTSxLQUFLLFNBQVMsRUFBRTtZQUN6QixNQUFNLE9BQU8sR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUMxQyxJQUFJLE9BQU8sS0FBSyxTQUFTLEVBQUU7Z0JBQzFCLE9BQU8sU0FBUyxDQUFDO2FBQ2pCO1lBQ0QsTUFBTSxHQUFHLEVBQUUsQ0FBQyxjQUFjLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQy9DLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxDQUFDO1NBQzVDO1FBQ0QsT0FBTyxNQUFNLENBQUM7SUFDZixDQUFDO0lBQ0QsbUJBQW1CO1FBQ2xCLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUM7SUFDdkMsQ0FBQztJQUNELHFCQUFxQixDQUFDLE9BQTJCO1FBQ2hELE9BQU8sRUFBRSxDQUFDLHFCQUFxQixDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzFDLENBQUM7SUFDRCxlQUFlLEdBQUcsRUFBRSxDQUFDLEdBQUcsQ0FBQyxlQUFlLENBQUM7SUFDekMsY0FBYyxHQUFHLEVBQUUsQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDO0lBQ3ZDLFVBQVUsR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQztJQUMvQixRQUFRLEdBQUcsRUFBRSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUM7SUFDM0IsYUFBYSxHQUFHLEVBQUUsQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDO0lBQ3JDLG9EQUFvRDtJQUNwRCxRQUFRLEdBQUcsRUFBRSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUM7Q0FDM0I7QUFyREQsOERBcURDIn0=

View File

@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as path from 'path';
export class StaticLanguageServiceHost implements ts.LanguageServiceHost {
private readonly _cmdLine: ts.ParsedCommandLine;
private readonly _scriptSnapshots: Map<string, ts.IScriptSnapshot> = new Map();
constructor(readonly projectPath: string) {
const existingOptions: Partial<ts.CompilerOptions> = {};
const parsed = ts.readConfigFile(projectPath, ts.sys.readFile);
if (parsed.error) {
throw parsed.error;
}
this._cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, path.dirname(projectPath), existingOptions);
if (this._cmdLine.errors.length > 0) {
throw parsed.error;
}
}
getCompilationSettings(): ts.CompilerOptions {
return this._cmdLine.options;
}
getScriptFileNames(): string[] {
return this._cmdLine.fileNames;
}
getScriptVersion(_fileName: string): string {
return '1';
}
getProjectVersion(): string {
return '1';
}
getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined {
let result: ts.IScriptSnapshot | undefined = this._scriptSnapshots.get(fileName);
if (result === undefined) {
const content = ts.sys.readFile(fileName);
if (content === undefined) {
return undefined;
}
result = ts.ScriptSnapshot.fromString(content);
this._scriptSnapshots.set(fileName, result);
}
return result;
}
getCurrentDirectory(): string {
return path.dirname(this.projectPath);
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
return ts.getDefaultLibFilePath(options);
}
directoryExists = ts.sys.directoryExists;
getDirectories = ts.sys.getDirectories;
fileExists = ts.sys.fileExists;
readFile = ts.sys.readFile;
readDirectory = ts.sys.readDirectory;
// this is necessary to make source references work.
realpath = ts.sys.realpath;
}

File diff suppressed because one or more lines are too long

View File

@ -38,6 +38,7 @@
"@types/through2": "^2.0.36",
"@types/tmp": "^0.2.1",
"@types/underscore": "^1.8.9",
"@types/workerpool": "^6.4.0",
"@types/xml2js": "0.0.33",
"@vscode/iconv-lite-umd": "0.7.0",
"@vscode/vsce": "^2.16.0",
@ -70,5 +71,8 @@
"tree-sitter": "https://github.com/joaomoreno/node-tree-sitter/releases/download/v0.20.0/tree-sitter-0.20.0.tgz",
"tree-sitter-typescript": "^0.20.1",
"vscode-gulp-watch": "^5.0.3"
},
"dependencies": {
"workerpool": "^6.4.0"
}
}

View File

@ -646,6 +646,13 @@
dependencies:
"@types/node" "*"
"@types/workerpool@^6.4.0":
version "6.4.0"
resolved "https://registry.yarnpkg.com/@types/workerpool/-/workerpool-6.4.0.tgz#c79292915dd08350d10e78e74687b6f401f270b8"
integrity sha512-SIF2/169pDsLKeM8GQGHkOFifGalDbZgiBSaLUnnlVSRsAOenkAvQ6h4uhV2W+PZZczS+8LQxACwNkSykdT91A==
dependencies:
"@types/node" "*"
"@types/xml2js@0.0.33":
version "0.0.33"
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.0.33.tgz#20c5dd6460245284d64a55690015b95e409fb7de"
@ -3017,6 +3024,11 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.2 || 2 || 3 || 4"
workerpool@^6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462"
integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A==
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"

View File

@ -8,12 +8,12 @@ const fs = require('fs');
const webpack = require('webpack');
const fancyLog = require('fancy-log');
const ansiColors = require('ansi-colors');
const { Mangler } = require('../build/lib/mangleTypeScript');
const { Mangler } = require('../build/lib/mangle/index');
/**
* Map of project paths to mangled file contents
*
* @type {Map<string, Map<string, { out: string; sourceMap?: string }>>}
* @type {Map<string, Promise<Map<string, { out: string; sourceMap?: string }>>>}
*/
const mangleMap = new Map();
@ -55,7 +55,7 @@ module.exports = async function (source, sourceMap, meta) {
const callback = this.async();
const fileContentsMap = getMangledFileContents(options.configFile);
const fileContentsMap = await getMangledFileContents(options.configFile);
const newContents = fileContentsMap.get(this.resourcePath);
callback(null, newContents?.out ?? source, sourceMap, meta);

View File

@ -74,6 +74,7 @@ export class ErrorHandler {
export const errorHandler = new ErrorHandler();
/** @skipMangle */
export function setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
errorHandler.setUnexpectedErrorHandler(newUnexpectedErrorHandler);
}

View File

@ -48,6 +48,8 @@ else {
* environments.
*
* Note: in web, this property is hardcoded to be `/`.
*
* @skipMangle
*/
export const cwd = safeProcess.cwd;

View File

@ -10,5 +10,5 @@
* supported in JSON.
* @param content the content to strip comments from
* @returns the content without comments
*/
*/
export function stripComments(content: string): string;

View File

@ -558,6 +558,7 @@ export class SimpleWorkerServer<H extends object> {
/**
* Called on the worker side
* @skipMangle
*/
export function create(postMessage: (msg: Message, transfer?: ArrayBuffer[]) => void): SimpleWorkerServer<any> {
return new SimpleWorkerServer(postMessage, null);

View File

@ -9,6 +9,8 @@ interface ICSSPluginConfig {
/**
* Invoked by the loader at run-time
*
* @skipMangle
*/
export function load(name: string, req: AMDLoader.IRelativeRequire, load: AMDLoader.IPluginLoadCallback, config: AMDLoader.IConfigurationOptions): void {
config = config || {};

View File

@ -297,7 +297,12 @@ export class TextAreaHandler extends ViewPart {
};
const textAreaWrapper = this._register(new TextAreaWrapper(this.textArea.domNode));
this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, textAreaWrapper, platform.OS, browser));
this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, textAreaWrapper, platform.OS, {
isAndroid: browser.isAndroid,
isChrome: browser.isChrome,
isFirefox: browser.isFirefox,
isSafari: browser.isSafari,
}));
this._register(this._textAreaInput.onKeyDown((e: IKeyboardEvent) => {
this._viewController.emitKeyDown(e);

View File

@ -121,7 +121,12 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string
}
};
const handler = new TextAreaInput(textAreaInputHost, new TextAreaWrapper(input), platform.OS, browser);
const handler = new TextAreaInput(textAreaInputHost, new TextAreaWrapper(input), platform.OS, {
isAndroid: browser.isAndroid,
isFirefox: browser.isFirefox,
isChrome: browser.isChrome,
isSafari: browser.isSafari,
});
const output = document.createElement('pre');
output.className = 'output';

View File

@ -123,6 +123,9 @@ export function localize(info: ILocalizeInfo, message: string, ...args: (string
*/
export function localize(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string;
/**
* @skipMangle
*/
export function localize(data: ILocalizeInfo | string, message: string, ...args: (string | number | boolean | undefined | null)[]): string {
return _format(message, args);
}
@ -133,18 +136,25 @@ export function localize(data: ILocalizeInfo | string, message: string, ...args:
* in order to ensure the loader plugin has been initialized before this function is called.
*/
export function getConfiguredDefaultLocale(stringFromLocalizeCall: string): string | undefined;
/**
* @skipMangle
*/
export function getConfiguredDefaultLocale(_: string): string | undefined {
// This returns undefined because this implementation isn't used and is overwritten by the loader
// when loaded.
return undefined;
}
/**
* @skipMangle
*/
export function setPseudoTranslation(value: boolean) {
isPseudo = value;
}
/**
* Invoked in a built product at run-time
* @skipMangle
*/
export function create(key: string, data: IBundledStrings & IConsumerAPI): IConsumerAPI {
return {
@ -155,10 +165,12 @@ export function create(key: string, data: IBundledStrings & IConsumerAPI): ICons
/**
* Invoked by the loader at run-time
* @skipMangle
*/
export function load(name: string, req: AMDLoader.IRelativeRequire, load: AMDLoader.IPluginLoadCallback, config: AMDLoader.IConfigurationOptions): void {
const pluginConfig: INLSPluginConfig = config['vs/nls'] ?? {};
if (!name || name.length === 0) {
// TODO: We need to give back the mangled names here
return load({
localize: localize,
getConfiguredDefaultLocale: () => pluginConfig.availableLanguages?.['*']

View File

@ -11,7 +11,6 @@ import { IV8Profile, Utils } from 'vs/platform/profiling/common/profiling';
import { IProfileModel, BottomUpSample, buildModel, BottomUpNode, processNode, CdpCallFrame } from 'vs/platform/profiling/common/profilingModel';
import { BottomUpAnalysis, IProfileAnalysisWorker, ProfilingOutput } from 'vs/platform/profiling/electron-sandbox/profileAnalysisWorkerService';
export function create(): IRequestHandler {
return new ProfileAnalysisWorker();
}

View File

@ -12,6 +12,8 @@ import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry';
* and can be used to add assertions. e.g. that registries are empty, etc.
*
* !! This is called directly by the testing framework.
*
* @skipMangle
*/
export function assertCleanState(): void {
// If this test fails, it is a clear indication that