api.js 16.5 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6
"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 });
7 8 9 10 11 12 13
const fs = require("fs");
const ts = require("typescript");
const path = require("path");
const util = require("gulp-util");
const tsfmt = require('../../tsfmt.json');
function log(message, ...rest) {
    util.log(util.colors.cyan('[monaco.d.ts]'), message, ...rest);
J
Joao Moreno 已提交
14
}
15 16
const SRC = path.join(__dirname, '../../src');
const OUT_ROOT = path.join(__dirname, '../../');
A
Alex Dima 已提交
17
exports.RECIPE_PATH = path.join(__dirname, './monaco.d.ts.recipe');
18
const DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts');
J
Joao Moreno 已提交
19
var CURRENT_PROCESSING_RULE = '';
20
function logErr(message, ...rest) {
J
Joao Moreno 已提交
21
    util.log(util.colors.red('[monaco.d.ts]'), 'WHILE HANDLING RULE: ', CURRENT_PROCESSING_RULE);
22
    util.log(util.colors.red('[monaco.d.ts]'), message, ...rest);
J
Joao Moreno 已提交
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
}
function moduleIdToPath(out, moduleId) {
    if (/\.d\.ts/.test(moduleId)) {
        return path.join(SRC, moduleId);
    }
    return path.join(OUT_ROOT, out, moduleId) + '.d.ts';
}
function isDeclaration(a) {
    return (a.kind === ts.SyntaxKind.InterfaceDeclaration
        || a.kind === ts.SyntaxKind.EnumDeclaration
        || a.kind === ts.SyntaxKind.ClassDeclaration
        || a.kind === ts.SyntaxKind.TypeAliasDeclaration
        || a.kind === ts.SyntaxKind.FunctionDeclaration
        || a.kind === ts.SyntaxKind.ModuleDeclaration);
}
function visitTopLevelDeclarations(sourceFile, visitor) {
39 40
    let stop = false;
    let visit = (node) => {
J
Joao Moreno 已提交
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
        if (stop) {
            return;
        }
        switch (node.kind) {
            case ts.SyntaxKind.InterfaceDeclaration:
            case ts.SyntaxKind.EnumDeclaration:
            case ts.SyntaxKind.ClassDeclaration:
            case ts.SyntaxKind.VariableStatement:
            case ts.SyntaxKind.TypeAliasDeclaration:
            case ts.SyntaxKind.FunctionDeclaration:
            case ts.SyntaxKind.ModuleDeclaration:
                stop = visitor(node);
        }
        if (stop) {
            return;
        }
        ts.forEachChild(node, visit);
    };
    visit(sourceFile);
}
function getAllTopLevelDeclarations(sourceFile) {
62 63
    let all = [];
    visitTopLevelDeclarations(sourceFile, (node) => {
J
Joao Moreno 已提交
64
        if (node.kind === ts.SyntaxKind.InterfaceDeclaration || node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ModuleDeclaration) {
65 66 67 68
            let interfaceDeclaration = node;
            let triviaStart = interfaceDeclaration.pos;
            let triviaEnd = interfaceDeclaration.name.pos;
            let triviaText = getNodeText(sourceFile, { pos: triviaStart, end: triviaEnd });
J
Joao Moreno 已提交
69 70 71 72 73
            if (triviaText.indexOf('@internal') === -1) {
                all.push(node);
            }
        }
        else {
74
            let nodeText = getNodeText(sourceFile, node);
J
Joao Moreno 已提交
75 76 77 78 79 80 81 82 83
            if (nodeText.indexOf('@internal') === -1) {
                all.push(node);
            }
        }
        return false /*continue*/;
    });
    return all;
}
function getTopLevelDeclaration(sourceFile, typeName) {
84 85
    let result = null;
    visitTopLevelDeclarations(sourceFile, (node) => {
M
Matt Bierner 已提交
86
        if (isDeclaration(node) && node.name) {
J
Joao Moreno 已提交
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
            if (node.name.text === typeName) {
                result = node;
                return true /*stop*/;
            }
            return false /*continue*/;
        }
        // node is ts.VariableStatement
        if (getNodeText(sourceFile, node).indexOf(typeName) >= 0) {
            result = node;
            return true /*stop*/;
        }
        return false /*continue*/;
    });
    return result;
}
function getNodeText(sourceFile, node) {
    return sourceFile.getFullText().substring(node.pos, node.end);
}
105 106
function hasModifier(modifiers, kind) {
    if (modifiers) {
107 108
        for (let i = 0; i < modifiers.length; i++) {
            let mod = modifiers[i];
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
            if (mod.kind === kind) {
                return true;
            }
        }
    }
    return false;
}
function isStatic(member) {
    return hasModifier(member.modifiers, ts.SyntaxKind.StaticKeyword);
}
function isDefaultExport(declaration) {
    return (hasModifier(declaration.modifiers, ts.SyntaxKind.DefaultKeyword)
        && hasModifier(declaration.modifiers, ts.SyntaxKind.ExportKeyword));
}
function getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage) {
124
    let result = getNodeText(sourceFile, declaration);
J
Joao Moreno 已提交
125
    if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) {
126 127 128 129 130 131
        let interfaceDeclaration = declaration;
        const staticTypeName = (isDefaultExport(interfaceDeclaration)
            ? `${importName}.default`
            : `${importName}.${declaration.name.text}`);
        let instanceTypeName = staticTypeName;
        const typeParametersCnt = (interfaceDeclaration.typeParameters ? interfaceDeclaration.typeParameters.length : 0);
132
        if (typeParametersCnt > 0) {
133 134
            let arr = [];
            for (let i = 0; i < typeParametersCnt; i++) {
135 136
                arr.push('any');
            }
137
            instanceTypeName = `${instanceTypeName}<${arr.join(',')}>`;
138
        }
139 140
        const members = interfaceDeclaration.members;
        members.forEach((member) => {
J
Joao Moreno 已提交
141
            try {
142
                let memberText = getNodeText(sourceFile, member);
J
Joao Moreno 已提交
143 144 145
                if (memberText.indexOf('@internal') >= 0 || memberText.indexOf('private') >= 0) {
                    result = result.replace(memberText, '');
                }
146
                else {
147
                    const memberName = member.name.text;
148
                    if (isStatic(member)) {
149
                        usage.push(`a = ${staticTypeName}.${memberName};`);
150 151
                    }
                    else {
152
                        usage.push(`a = (<${instanceTypeName}>b).${memberName};`);
153 154
                    }
                }
J
Joao Moreno 已提交
155 156 157 158 159 160 161 162 163 164 165 166
            }
            catch (err) {
                // life..
            }
        });
    }
    result = result.replace(/export default/g, 'export');
    result = result.replace(/export declare/g, 'export');
    return result;
}
function format(text) {
    // Parse the source text
167
    let sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true);
J
Joao Moreno 已提交
168
    // Get the formatting edits on the input sources
169
    let edits = ts.formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt);
J
Joao Moreno 已提交
170 171 172 173 174
    // Apply the edits on the input code
    return applyEdits(text, edits);
    function getRuleProvider(options) {
        // Share this between multiple formatters using the same options.
        // This represents the bulk of the space the formatter uses.
M
Matt Bierner 已提交
175
        return ts.formatting.getFormatContext(options);
J
Joao Moreno 已提交
176 177 178
    }
    function applyEdits(text, edits) {
        // Apply edits in reverse on the existing text
179 180 181 182 183
        let result = text;
        for (let i = edits.length - 1; i >= 0; i--) {
            let change = edits[i];
            let head = result.slice(0, change.span.start);
            let tail = result.slice(change.span.start + change.span.length);
J
Joao Moreno 已提交
184 185 186 187 188 189 190
            result = head + change.newText + tail;
        }
        return result;
    }
}
function createReplacer(data) {
    data = data || '';
191 192 193
    let rawDirectives = data.split(';');
    let directives = [];
    rawDirectives.forEach((rawDirective) => {
J
Joao Moreno 已提交
194 195 196
        if (rawDirective.length === 0) {
            return;
        }
197 198 199
        let pieces = rawDirective.split('=>');
        let findStr = pieces[0];
        let replaceStr = pieces[1];
J
Joao Moreno 已提交
200 201 202 203
        findStr = findStr.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&');
        findStr = '\\b' + findStr + '\\b';
        directives.push([new RegExp(findStr, 'g'), replaceStr]);
    });
204 205
    return (str) => {
        for (let i = 0; i < directives.length; i++) {
J
Joao Moreno 已提交
206 207 208 209 210
            str = str.replace(directives[i][0], directives[i][1]);
        }
        return str;
    };
}
211
function generateDeclarationFile(recipe, sourceFileGetter) {
212 213 214 215 216 217 218 219 220 221 222
    const endl = /\r\n/.test(recipe) ? '\r\n' : '\n';
    let lines = recipe.split(endl);
    let result = [];
    let usageCounter = 0;
    let usageImports = [];
    let usage = [];
    usage.push(`var a;`);
    usage.push(`var b;`);
    const generateUsageImport = (moduleId) => {
        let importName = 'm' + (++usageCounter);
        usageImports.push(`import * as ${importName} from './${moduleId.replace(/\.d\.ts$/, '')}';`);
223 224
        return importName;
    };
225 226
    lines.forEach(line => {
        let m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
J
Joao Moreno 已提交
227 228
        if (m1) {
            CURRENT_PROCESSING_RULE = line;
229
            let moduleId = m1[1];
230
            const sourceFile = sourceFileGetter(moduleId);
231
            if (!sourceFile) {
J
Joao Moreno 已提交
232 233
                return;
            }
234 235 236 237
            const importName = generateUsageImport(moduleId);
            let replacer = createReplacer(m1[2]);
            let typeNames = m1[3].split(/,/);
            typeNames.forEach((typeName) => {
J
Joao Moreno 已提交
238 239 240 241
                typeName = typeName.trim();
                if (typeName.length === 0) {
                    return;
                }
242
                let declaration = getTopLevelDeclaration(sourceFile, typeName);
J
Joao Moreno 已提交
243 244 245 246
                if (!declaration) {
                    logErr('Cannot find type ' + typeName);
                    return;
                }
247
                result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage)));
J
Joao Moreno 已提交
248 249 250
            });
            return;
        }
251
        let m2 = line.match(/^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
J
Joao Moreno 已提交
252 253
        if (m2) {
            CURRENT_PROCESSING_RULE = line;
254
            let moduleId = m2[1];
255
            const sourceFile = sourceFileGetter(moduleId);
256
            if (!sourceFile) {
J
Joao Moreno 已提交
257 258
                return;
            }
259 260 261 262 263 264
            const importName = generateUsageImport(moduleId);
            let replacer = createReplacer(m2[2]);
            let typeNames = m2[3].split(/,/);
            let typesToExcludeMap = {};
            let typesToExcludeArr = [];
            typeNames.forEach((typeName) => {
J
Joao Moreno 已提交
265 266 267 268
                typeName = typeName.trim();
                if (typeName.length === 0) {
                    return;
                }
269 270
                typesToExcludeMap[typeName] = true;
                typesToExcludeArr.push(typeName);
J
Joao Moreno 已提交
271
            });
272
            getAllTopLevelDeclarations(sourceFile).forEach((declaration) => {
M
Matt Bierner 已提交
273
                if (isDeclaration(declaration) && declaration.name) {
274
                    if (typesToExcludeMap[declaration.name.text]) {
J
Joao Moreno 已提交
275 276 277 278 279
                        return;
                    }
                }
                else {
                    // node is ts.VariableStatement
280 281 282
                    let nodeText = getNodeText(sourceFile, declaration);
                    for (let i = 0; i < typesToExcludeArr.length; i++) {
                        if (nodeText.indexOf(typesToExcludeArr[i]) >= 0) {
J
Joao Moreno 已提交
283 284 285 286
                            return;
                        }
                    }
                }
287
                result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage)));
J
Joao Moreno 已提交
288 289 290 291 292
            });
            return;
        }
        result.push(line);
    });
293
    let resultTxt = result.join(endl);
J
Joao Moreno 已提交
294 295 296
    resultTxt = resultTxt.replace(/\bURI\b/g, 'Uri');
    resultTxt = resultTxt.replace(/\bEvent</g, 'IEvent<');
    resultTxt = format(resultTxt);
297 298
    return [
        resultTxt,
299
        `${usageImports.join('\n')}\n\n${usage.join('\n')}`
300
    ];
J
Joao Moreno 已提交
301
}
302
function getIncludesInRecipe() {
A
Alex Dima 已提交
303
    let recipe = fs.readFileSync(exports.RECIPE_PATH).toString();
304 305 306 307
    let lines = recipe.split(/\r\n|\n|\r/);
    let result = [];
    lines.forEach(line => {
        let m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
J
Joao Moreno 已提交
308
        if (m1) {
309
            let moduleId = m1[1];
310
            result.push(moduleId);
J
Joao Moreno 已提交
311 312
            return;
        }
313
        let m2 = line.match(/^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/);
J
Joao Moreno 已提交
314
        if (m2) {
315
            let moduleId = m2[1];
316
            result.push(moduleId);
J
Joao Moreno 已提交
317 318 319 320 321
            return;
        }
    });
    return result;
}
A
Alex Dima 已提交
322
exports.getIncludesInRecipe = getIncludesInRecipe;
323
function getFilesToWatch(out) {
324
    return getIncludesInRecipe().map((moduleId) => moduleIdToPath(out, moduleId));
325
}
J
Joao Moreno 已提交
326
exports.getFilesToWatch = getFilesToWatch;
327
function _run(sourceFileGetter) {
J
Joao Moreno 已提交
328
    log('Starting monaco.d.ts generation');
A
Alex Dima 已提交
329
    let recipe = fs.readFileSync(exports.RECIPE_PATH).toString();
330
    let [result, usageContent] = generateDeclarationFile(recipe, sourceFileGetter);
331
    let currentContent = fs.readFileSync(DECLARATION_PATH).toString();
J
Joao Moreno 已提交
332
    log('Finished monaco.d.ts generation');
333 334
    const one = currentContent.replace(/\r\n/gm, '\n');
    const other = result.replace(/\r\n/gm, '\n');
335
    const isTheSame = (one === other);
J
Joao Moreno 已提交
336 337
    return {
        content: result,
338
        usageContent: usageContent,
J
Joao Moreno 已提交
339
        filePath: DECLARATION_PATH,
340
        isTheSame
J
Joao Moreno 已提交
341 342
    };
}
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
function run(out, inputFiles) {
    let SOURCE_FILE_MAP = {};
    const sourceFileGetter = (moduleId) => {
        if (!SOURCE_FILE_MAP[moduleId]) {
            let filePath = path.normalize(moduleIdToPath(out, moduleId));
            if (!inputFiles.hasOwnProperty(filePath)) {
                logErr('CANNOT FIND FILE ' + filePath + '. YOU MIGHT NEED TO RESTART gulp');
                return null;
            }
            let fileContents = inputFiles[filePath];
            let sourceFile = ts.createSourceFile(filePath, fileContents, ts.ScriptTarget.ES5);
            SOURCE_FILE_MAP[moduleId] = sourceFile;
        }
        return SOURCE_FILE_MAP[moduleId];
    };
    return _run(sourceFileGetter);
}
J
Joao Moreno 已提交
360
exports.run = run;
361 362 363 364 365 366 367 368
function run2(out, sourceFileMap) {
    const sourceFileGetter = (moduleId) => {
        let filePath = path.normalize(moduleIdToPath(out, moduleId));
        return sourceFileMap[filePath];
    };
    return _run(sourceFileGetter);
}
exports.run2 = run2;
J
Joao Moreno 已提交
369 370 371 372
function complainErrors() {
    logErr('Not running monaco.d.ts generation due to compile errors');
}
exports.complainErrors = complainErrors;
373 374
class TypeScriptLanguageServiceHost {
    constructor(libs, files, compilerOptions) {
375 376 377 378 379
        this._libs = libs;
        this._files = files;
        this._compilerOptions = compilerOptions;
    }
    // --- language service host ---------------
380
    getCompilationSettings() {
381
        return this._compilerOptions;
382 383
    }
    getScriptFileNames() {
384 385 386
        return ([]
            .concat(Object.keys(this._libs))
            .concat(Object.keys(this._files)));
387 388
    }
    getScriptVersion(_fileName) {
389
        return '1';
390 391
    }
    getProjectVersion() {
392
        return '1';
393 394
    }
    getScriptSnapshot(fileName) {
395 396 397 398 399 400 401 402 403
        if (this._files.hasOwnProperty(fileName)) {
            return ts.ScriptSnapshot.fromString(this._files[fileName]);
        }
        else if (this._libs.hasOwnProperty(fileName)) {
            return ts.ScriptSnapshot.fromString(this._libs[fileName]);
        }
        else {
            return ts.ScriptSnapshot.fromString('');
        }
404 405
    }
    getScriptKind(_fileName) {
406
        return ts.ScriptKind.TS;
407 408
    }
    getCurrentDirectory() {
409
        return '';
410 411
    }
    getDefaultLibFileName(_options) {
412
        return 'defaultLib:es5';
413 414
    }
    isDefaultLibFileName(fileName) {
415
        return fileName === this.getDefaultLibFileName(this._compilerOptions);
416 417
    }
}
A
Alex Dima 已提交
418
exports.TypeScriptLanguageServiceHost = TypeScriptLanguageServiceHost;
419
function execute() {
420 421 422 423
    const OUTPUT_FILES = {};
    const SRC_FILES = {};
    const SRC_FILE_TO_EXPECTED_NAME = {};
    getIncludesInRecipe().forEach((moduleId) => {
424
        if (/\.d\.ts$/.test(moduleId)) {
425 426
            let fileName = path.join(SRC, moduleId);
            OUTPUT_FILES[moduleIdToPath('src', moduleId)] = fs.readFileSync(fileName).toString();
427 428
            return;
        }
429
        let fileName = path.join(SRC, moduleId) + '.ts';
430 431 432
        SRC_FILES[fileName] = fs.readFileSync(fileName).toString();
        SRC_FILE_TO_EXPECTED_NAME[fileName] = moduleIdToPath('src', moduleId);
    });
433
    const languageService = ts.createLanguageService(new TypeScriptLanguageServiceHost({}, SRC_FILES, {}));
434
    var t1 = Date.now();
435 436
    Object.keys(SRC_FILES).forEach((fileName) => {
        const emitOutput = languageService.getEmitOutput(fileName, true);
437 438
        OUTPUT_FILES[SRC_FILE_TO_EXPECTED_NAME[fileName]] = emitOutput.outputFiles[0].text;
    });
439
    console.log(`Generating .d.ts took ${Date.now() - t1} ms`);
A
Alex Dima 已提交
440
    return run('src', OUTPUT_FILES);
441
}
A
Alex Dima 已提交
442
exports.execute = execute;