2022-11-11 12:48:21 +00:00
|
|
|
"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 });
|
2022-11-14 12:51:21 +00:00
|
|
|
exports.Mangler = void 0;
|
2022-11-11 12:48:21 +00:00
|
|
|
const ts = require("typescript");
|
|
|
|
const fancy_log_1 = require("fancy-log");
|
|
|
|
const path_1 = require("path");
|
|
|
|
const fs = require("fs");
|
2022-11-14 12:51:21 +00:00
|
|
|
const Vinyl = require("vinyl");
|
2022-11-11 12:48:21 +00:00
|
|
|
class ShortIdent {
|
|
|
|
static _keywords = new Set(['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
|
|
'default', 'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if',
|
|
|
|
'import', 'in', 'instanceof', 'let', 'new', 'null', 'return', 'static', 'super', 'switch', 'this', 'throw',
|
|
|
|
'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield']);
|
|
|
|
static alphabet = [];
|
|
|
|
static {
|
|
|
|
for (let i = 97; i < 122; i++) {
|
|
|
|
this.alphabet.push(String.fromCharCode(i));
|
|
|
|
}
|
|
|
|
for (let i = 65; i < 90; i++) {
|
|
|
|
this.alphabet.push(String.fromCharCode(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_value = 0;
|
|
|
|
_isNameTaken;
|
|
|
|
constructor(isNameTaken) {
|
2022-11-14 12:51:21 +00:00
|
|
|
this._isNameTaken = name => ShortIdent._keywords.has(name) || isNameTaken(name);
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
|
|
|
next() {
|
|
|
|
const candidate = ShortIdent.convert(this._value);
|
|
|
|
this._value++;
|
|
|
|
if (this._isNameTaken(candidate)) {
|
|
|
|
// try again
|
|
|
|
return this.next();
|
|
|
|
}
|
|
|
|
return candidate;
|
|
|
|
}
|
|
|
|
static convert(n) {
|
|
|
|
const base = this.alphabet.length;
|
|
|
|
let result = '';
|
|
|
|
do {
|
2022-11-14 12:51:21 +00:00
|
|
|
const rest = n % base;
|
2022-11-11 12:48:21 +00:00
|
|
|
result += this.alphabet[rest];
|
|
|
|
n = (n / base) | 0;
|
|
|
|
} while (n > 0);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var FieldType;
|
|
|
|
(function (FieldType) {
|
|
|
|
FieldType[FieldType["Public"] = 0] = "Public";
|
|
|
|
FieldType[FieldType["Protected"] = 1] = "Protected";
|
|
|
|
FieldType[FieldType["Private"] = 2] = "Private";
|
|
|
|
})(FieldType || (FieldType = {}));
|
|
|
|
class ClassData {
|
|
|
|
fileName;
|
|
|
|
node;
|
|
|
|
fields = new Map();
|
|
|
|
replacements;
|
|
|
|
parent;
|
|
|
|
children;
|
2022-11-11 16:16:29 +00:00
|
|
|
constructor(fileName, node) {
|
2022-11-11 12:48:21 +00:00
|
|
|
// analyse all fields (properties and methods). Find usages of all protected and
|
|
|
|
// private ones and keep track of all public ones (to prevent naming collisions)
|
|
|
|
this.fileName = fileName;
|
|
|
|
this.node = node;
|
|
|
|
const candidates = [];
|
|
|
|
for (const member of node.members) {
|
|
|
|
if (ts.isMethodDeclaration(member)) {
|
|
|
|
// method `foo() {}`
|
|
|
|
candidates.push(member);
|
|
|
|
}
|
|
|
|
else if (ts.isPropertyDeclaration(member)) {
|
|
|
|
// property `foo = 234`
|
|
|
|
candidates.push(member);
|
|
|
|
}
|
|
|
|
else if (ts.isGetAccessor(member)) {
|
|
|
|
// getter: `get foo() { ... }`
|
|
|
|
candidates.push(member);
|
|
|
|
}
|
|
|
|
else if (ts.isSetAccessor(member)) {
|
|
|
|
// setter: `set foo() { ... }`
|
|
|
|
candidates.push(member);
|
|
|
|
}
|
|
|
|
else if (ts.isConstructorDeclaration(member)) {
|
|
|
|
// constructor-prop:`constructor(private foo) {}`
|
|
|
|
for (const param of member.parameters) {
|
|
|
|
if (hasModifier(param, ts.SyntaxKind.PrivateKeyword)
|
|
|
|
|| hasModifier(param, ts.SyntaxKind.ProtectedKeyword)
|
|
|
|
|| hasModifier(param, ts.SyntaxKind.PublicKeyword)
|
|
|
|
|| hasModifier(param, ts.SyntaxKind.ReadonlyKeyword)) {
|
|
|
|
candidates.push(param);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const member of candidates) {
|
|
|
|
const ident = ClassData._getMemberName(member);
|
|
|
|
if (!ident) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const type = ClassData._getFieldType(member);
|
2022-11-11 16:16:29 +00:00
|
|
|
this.fields.set(ident, { type, pos: member.name.getStart() });
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
static _getMemberName(node) {
|
|
|
|
if (!node.name) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const { name } = node;
|
|
|
|
let ident = name.getText();
|
|
|
|
if (name.kind === ts.SyntaxKind.ComputedPropertyName) {
|
|
|
|
if (name.expression.kind !== ts.SyntaxKind.StringLiteral) {
|
|
|
|
// unsupported: [Symbol.foo] or [abc + 'field']
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// ['foo']
|
|
|
|
ident = name.expression.getText().slice(1, -1);
|
|
|
|
}
|
|
|
|
return ident;
|
|
|
|
}
|
|
|
|
static _getFieldType(node) {
|
|
|
|
if (hasModifier(node, ts.SyntaxKind.PrivateKeyword)) {
|
|
|
|
return 2 /* FieldType.Private */;
|
|
|
|
}
|
|
|
|
else if (hasModifier(node, ts.SyntaxKind.ProtectedKeyword)) {
|
|
|
|
return 1 /* FieldType.Protected */;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return 0 /* FieldType.Public */;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static _shouldMangle(type) {
|
2022-11-11 16:16:29 +00:00
|
|
|
return type === 2 /* FieldType.Private */
|
|
|
|
|| type === 1 /* FieldType.Protected */;
|
|
|
|
}
|
|
|
|
static makeImplicitPublicActuallyPublic(data) {
|
|
|
|
// TS-HACK
|
|
|
|
// A subtype can make an inherited protected field public. To prevent accidential
|
|
|
|
// mangling of public fields we mark the original (protected) fields as public...
|
|
|
|
for (const [name, info] of data.fields) {
|
|
|
|
if (info.type !== 0 /* FieldType.Public */) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let parent = data.parent;
|
|
|
|
while (parent) {
|
|
|
|
if (parent.fields.get(name)?.type === 1 /* FieldType.Protected */) {
|
2022-11-14 12:51:21 +00:00
|
|
|
console.warn(`WARN: protected became PUBLIC: '${name}' defined ${parent.fileName}#${info.pos}, PUBLIC via ${data.fileName} (${info.pos})`);
|
2022-11-11 16:16:29 +00:00
|
|
|
parent.fields.get(name).type = 0 /* FieldType.Public */;
|
|
|
|
}
|
|
|
|
parent = parent.parent;
|
|
|
|
}
|
|
|
|
}
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
|
|
|
static fillInReplacement(data) {
|
|
|
|
if (data.replacements) {
|
|
|
|
// already done
|
|
|
|
return;
|
|
|
|
}
|
2022-11-11 16:16:29 +00:00
|
|
|
// fill in parents first
|
2022-11-11 12:48:21 +00:00
|
|
|
if (data.parent) {
|
|
|
|
ClassData.fillInReplacement(data.parent);
|
|
|
|
}
|
2022-11-11 16:16:29 +00:00
|
|
|
data.replacements = new Map();
|
2022-11-11 12:48:21 +00:00
|
|
|
const identPool = new ShortIdent(name => {
|
2022-11-11 16:16:29 +00:00
|
|
|
// locally taken
|
|
|
|
if (data._isNameTaken(name)) {
|
|
|
|
return true;
|
|
|
|
}
|
2022-11-11 12:48:21 +00:00
|
|
|
// parents
|
2022-11-11 16:16:29 +00:00
|
|
|
let parent = data.parent;
|
|
|
|
while (parent) {
|
|
|
|
if (parent._isNameTaken(name)) {
|
2022-11-11 12:48:21 +00:00
|
|
|
return true;
|
|
|
|
}
|
2022-11-11 16:16:29 +00:00
|
|
|
parent = parent.parent;
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
|
|
|
// children
|
|
|
|
if (data.children) {
|
|
|
|
const stack = [...data.children];
|
|
|
|
while (stack.length) {
|
|
|
|
const node = stack.pop();
|
2022-11-11 16:16:29 +00:00
|
|
|
if (node._isNameTaken(name)) {
|
2022-11-11 12:48:21 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (node.children) {
|
|
|
|
stack.push(...node.children);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-11-11 16:16:29 +00:00
|
|
|
return false;
|
2022-11-11 12:48:21 +00:00
|
|
|
});
|
|
|
|
for (const [name, info] of data.fields) {
|
|
|
|
if (ClassData._shouldMangle(info.type)) {
|
|
|
|
const shortName = identPool.next();
|
|
|
|
data.replacements.set(name, shortName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// a name is taken when a field that doesn't get mangled exists or
|
|
|
|
// when the name is already in use for replacement
|
2022-11-11 16:16:29 +00:00
|
|
|
_isNameTaken(name) {
|
2022-11-11 12:48:21 +00:00
|
|
|
if (this.fields.has(name) && !ClassData._shouldMangle(this.fields.get(name).type)) {
|
|
|
|
// public field
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (this.replacements) {
|
|
|
|
for (const shortName of this.replacements.values()) {
|
|
|
|
if (shortName === name) {
|
|
|
|
// replaced already (happens wih super types)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.node.getSourceFile().identifiers instanceof Map) {
|
2022-11-11 16:16:29 +00:00
|
|
|
// taken by any other usage
|
2022-11-11 12:48:21 +00:00
|
|
|
if (this.node.getSourceFile().identifiers.has(name)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2022-11-11 16:16:29 +00:00
|
|
|
lookupShortName(name) {
|
|
|
|
let value = this.replacements.get(name);
|
|
|
|
let parent = this.parent;
|
|
|
|
while (parent) {
|
|
|
|
if (parent.replacements.has(name) && parent.fields.get(name)?.type === 1 /* FieldType.Protected */) {
|
|
|
|
value = parent.replacements.get(name) ?? value;
|
|
|
|
}
|
|
|
|
parent = parent.parent;
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
2022-11-11 12:48:21 +00:00
|
|
|
// --- parent chaining
|
2022-11-14 12:51:21 +00:00
|
|
|
addChild(child) {
|
2022-11-11 12:48:21 +00:00
|
|
|
this.children ??= [];
|
|
|
|
this.children.push(child);
|
|
|
|
child.parent = this;
|
|
|
|
}
|
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
class StaticLanguageServiceHost {
|
|
|
|
cmdLine;
|
|
|
|
_scriptSnapshots = new Map();
|
|
|
|
constructor(cmdLine) {
|
|
|
|
this.cmdLine = cmdLine;
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
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);
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
return result;
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
getCurrentDirectory() {
|
|
|
|
return (0, path_1.dirname)(projectPath);
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
getDefaultLibFileName(options) {
|
|
|
|
return ts.getDefaultLibFilePath(options);
|
2022-11-11 16:16:29 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
class Mangler {
|
|
|
|
projectPath;
|
|
|
|
allClassDataByKey = new Map();
|
|
|
|
service;
|
|
|
|
constructor(projectPath) {
|
|
|
|
this.projectPath = projectPath;
|
|
|
|
const existingOptions = {};
|
|
|
|
const parsed = ts.readConfigFile(projectPath, ts.sys.readFile);
|
|
|
|
if (parsed.error) {
|
|
|
|
console.log(fancy_log_1.error);
|
|
|
|
throw parsed.error;
|
|
|
|
}
|
|
|
|
const cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, (0, path_1.dirname)(projectPath), existingOptions);
|
|
|
|
if (cmdLine.errors.length > 0) {
|
|
|
|
console.log(fancy_log_1.error);
|
|
|
|
throw parsed.error;
|
|
|
|
}
|
|
|
|
const host = new StaticLanguageServiceHost(cmdLine);
|
|
|
|
this.service = ts.createLanguageService(host);
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
// step 1: collect all class data and store it by symbols
|
|
|
|
// step 2: hook up extends-chaines and populate field replacement maps
|
|
|
|
// step 3: generate and apply rewrites
|
|
|
|
async mangle() {
|
|
|
|
// (1) find all classes and field info
|
|
|
|
const visit = (node) => {
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
ts.forEachChild(node, visit);
|
|
|
|
};
|
|
|
|
for (const file of this.service.getProgram().getSourceFiles()) {
|
|
|
|
if (!file.isDeclarationFile) {
|
|
|
|
ts.forEachChild(file, visit);
|
|
|
|
}
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
console.log(`done COLLECTING ${this.allClassDataByKey.size} classes`);
|
|
|
|
const setupParents = (data) => {
|
|
|
|
const extendsClause = data.node.heritageClauses?.find(h => h.token === ts.SyntaxKind.ExtendsKeyword);
|
|
|
|
if (!extendsClause) {
|
|
|
|
// no EXTENDS-clause
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const info = this.service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd());
|
|
|
|
if (!info || info.length === 0) {
|
|
|
|
// throw new Error('SUPER type not found');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (info.length !== 1) {
|
|
|
|
// inherits from declared/library type
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const [definition] = info;
|
|
|
|
const key = `${definition.fileName}|${definition.textSpan.start}`;
|
|
|
|
const parent = this.allClassDataByKey.get(key);
|
|
|
|
if (!parent) {
|
|
|
|
// throw new Error(`SUPER type not found: ${key}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
parent.addChild(data);
|
|
|
|
};
|
|
|
|
// (1.1) connect all class info
|
|
|
|
for (const data of this.allClassDataByKey.values()) {
|
|
|
|
setupParents(data);
|
|
|
|
}
|
|
|
|
// (1.2) TS-HACK: mark implicit-public protected field as public
|
|
|
|
for (const data of this.allClassDataByKey.values()) {
|
|
|
|
ClassData.makeImplicitPublicActuallyPublic(data);
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
// (2) fill in replacement strings
|
|
|
|
for (const data of this.allClassDataByKey.values()) {
|
|
|
|
ClassData.fillInReplacement(data);
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
console.log(`done creating REPLACEMENTS`);
|
|
|
|
const editsByFile = new Map();
|
|
|
|
const appendEdit = (fileName, edit) => {
|
|
|
|
const edits = editsByFile.get(fileName);
|
|
|
|
if (!edits) {
|
|
|
|
editsByFile.set(fileName, [edit]);
|
2022-11-11 16:16:29 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
else {
|
|
|
|
edits.push(edit);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
for (const data of this.allClassDataByKey.values()) {
|
|
|
|
if (hasModifier(data.node, ts.SyntaxKind.DeclareKeyword)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
fields: for (const [name, info] of data.fields) {
|
|
|
|
if (!ClassData._shouldMangle(info.type)) {
|
2022-11-11 16:16:29 +00:00
|
|
|
continue fields;
|
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
// TS-HACK: protected became public via 'some' child
|
|
|
|
// and because of that we might need to ignore this now
|
|
|
|
let parent = data.parent;
|
|
|
|
while (parent) {
|
|
|
|
if (parent.fields.get(name)?.type === 0 /* FieldType.Public */) {
|
|
|
|
continue fields;
|
|
|
|
}
|
|
|
|
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
|
|
|
|
});
|
|
|
|
}
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
console.log(`done preparing EDITS for ${editsByFile.size} files`);
|
|
|
|
// (4) apply renames
|
|
|
|
let savedBytes = 0;
|
|
|
|
const result = [];
|
|
|
|
for (const item of this.service.getProgram().getSourceFiles()) {
|
|
|
|
let newFullText;
|
|
|
|
const edits = editsByFile.get(item.fileName);
|
|
|
|
if (!edits) {
|
|
|
|
// just copy
|
|
|
|
newFullText = item.getFullText();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// apply renames
|
|
|
|
edits.sort((a, b) => b.offset - a.offset);
|
|
|
|
const characters = item.getFullText().split('');
|
|
|
|
let lastEdit;
|
|
|
|
for (const edit of edits) {
|
|
|
|
if (lastEdit) {
|
|
|
|
if (lastEdit.offset === edit.offset) {
|
|
|
|
//
|
|
|
|
if (lastEdit.length !== edit.length || lastEdit.newText !== edit.newText) {
|
|
|
|
console.log('OVERLAPPING edit', item.fileName, edit.offset, edits);
|
|
|
|
throw new Error('OVERLAPPING edit');
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
continue;
|
|
|
|
}
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
lastEdit = edit;
|
|
|
|
const removed = characters.splice(edit.offset, edit.length, edit.newText);
|
|
|
|
savedBytes += removed.length - edit.newText.length;
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
newFullText = characters.join('');
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
const projectBase = (0, path_1.dirname)(projectPath);
|
|
|
|
const newProjectBase = (0, path_1.join)((0, path_1.dirname)(projectBase), (0, path_1.basename)(projectBase) + '-mangle');
|
|
|
|
const newFilePath = (0, path_1.join)(newProjectBase, (0, path_1.relative)(projectBase, item.fileName));
|
|
|
|
const file = new Vinyl({ path: newFilePath, contents: Buffer.from(newFullText) });
|
|
|
|
result.push(file);
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
console.log(`DONE saved ${savedBytes / 1000}kb`);
|
|
|
|
return result;
|
2022-11-11 12:48:21 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-14 12:51:21 +00:00
|
|
|
exports.Mangler = Mangler;
|
|
|
|
// --- ast utils
|
|
|
|
function hasModifier(node, kind) {
|
|
|
|
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
|
|
return Boolean(modifiers?.find(mode => mode.kind === kind));
|
|
|
|
}
|
|
|
|
const projectPath = 1
|
|
|
|
? (0, path_1.join)(__dirname, '../../src/tsconfig.json')
|
|
|
|
: '/Users/jrieken/Code/_samples/3wm/mangePrivate/tsconfig.json';
|
|
|
|
new Mangler(projectPath).mangle().then(async (files) => {
|
|
|
|
for (const file of files) {
|
|
|
|
await fs.promises.writeFile(file.path, file.contents);
|
|
|
|
}
|
|
|
|
});
|