mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode merge-base (#22780)
* Revert "Revert "Merge from vscode merge-base (#22769)" (#22779)"
This reverts commit 47a1745180.
* Fix notebook download task
* Remove done call from extensions-ci
This commit is contained in:
491
build/lib/tsb/builder.js
Normal file
491
build/lib/tsb/builder.js
Normal file
@@ -0,0 +1,491 @@
|
||||
"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.createTypeScriptBuilder = exports.CancellationToken = void 0;
|
||||
const fs_1 = require("fs");
|
||||
const path = require("path");
|
||||
const crypto = require("crypto");
|
||||
const utils = require("./utils");
|
||||
const colors = require("ansi-colors");
|
||||
const ts = require("typescript");
|
||||
const Vinyl = require("vinyl");
|
||||
var CancellationToken;
|
||||
(function (CancellationToken) {
|
||||
CancellationToken.None = {
|
||||
isCancellationRequested() { return false; }
|
||||
};
|
||||
})(CancellationToken = exports.CancellationToken || (exports.CancellationToken = {}));
|
||||
function normalize(path) {
|
||||
return path.replace(/\\/g, '/');
|
||||
}
|
||||
function createTypeScriptBuilder(config, projectFile, cmd) {
|
||||
const _log = config.logFn;
|
||||
const host = new LanguageServiceHost(cmd, projectFile, _log);
|
||||
const service = ts.createLanguageService(host, ts.createDocumentRegistry());
|
||||
const lastBuildVersion = Object.create(null);
|
||||
const lastDtsHash = Object.create(null);
|
||||
const userWantsDeclarations = cmd.options.declaration;
|
||||
let oldErrors = Object.create(null);
|
||||
let headUsed = process.memoryUsage().heapUsed;
|
||||
let emitSourceMapsInStream = true;
|
||||
// always emit declaraction files
|
||||
host.getCompilationSettings().declaration = true;
|
||||
function file(file) {
|
||||
// support gulp-sourcemaps
|
||||
if (file.sourceMap) {
|
||||
emitSourceMapsInStream = false;
|
||||
}
|
||||
if (!file.contents) {
|
||||
host.removeScriptSnapshot(file.path);
|
||||
}
|
||||
else {
|
||||
host.addScriptSnapshot(file.path, new VinylScriptSnapshot(file));
|
||||
}
|
||||
}
|
||||
function baseFor(snapshot) {
|
||||
if (snapshot instanceof VinylScriptSnapshot) {
|
||||
return cmd.options.outDir || snapshot.getBase();
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
function isExternalModule(sourceFile) {
|
||||
return sourceFile.externalModuleIndicator
|
||||
|| /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText());
|
||||
}
|
||||
function build(out, onError, token = CancellationToken.None) {
|
||||
function checkSyntaxSoon(fileName) {
|
||||
return new Promise(resolve => {
|
||||
process.nextTick(function () {
|
||||
if (!host.getScriptSnapshot(fileName, false)) {
|
||||
resolve([]); // no script, no problems
|
||||
}
|
||||
else {
|
||||
resolve(service.getSyntacticDiagnostics(fileName));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function checkSemanticsSoon(fileName) {
|
||||
return new Promise(resolve => {
|
||||
process.nextTick(function () {
|
||||
if (!host.getScriptSnapshot(fileName, false)) {
|
||||
resolve([]); // no script, no problems
|
||||
}
|
||||
else {
|
||||
resolve(service.getSemanticDiagnostics(fileName));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function emitSoon(fileName) {
|
||||
return new Promise(resolve => {
|
||||
process.nextTick(function () {
|
||||
if (/\.d\.ts$/.test(fileName)) {
|
||||
// if it's already a d.ts file just emit it signature
|
||||
const snapshot = host.getScriptSnapshot(fileName);
|
||||
const signature = crypto.createHash('md5')
|
||||
.update(snapshot.getText(0, snapshot.getLength()))
|
||||
.digest('base64');
|
||||
return resolve({
|
||||
fileName,
|
||||
signature,
|
||||
files: []
|
||||
});
|
||||
}
|
||||
const output = service.getEmitOutput(fileName);
|
||||
const files = [];
|
||||
let signature;
|
||||
for (const file of output.outputFiles) {
|
||||
if (!emitSourceMapsInStream && /\.js\.map$/.test(file.name)) {
|
||||
continue;
|
||||
}
|
||||
if (/\.d\.ts$/.test(file.name)) {
|
||||
signature = crypto.createHash('md5')
|
||||
.update(file.text)
|
||||
.digest('base64');
|
||||
if (!userWantsDeclarations) {
|
||||
// don't leak .d.ts files if users don't want them
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const vinyl = new Vinyl({
|
||||
path: file.name,
|
||||
contents: Buffer.from(file.text),
|
||||
base: !config._emitWithoutBasePath && baseFor(host.getScriptSnapshot(fileName)) || undefined
|
||||
});
|
||||
if (!emitSourceMapsInStream && /\.js$/.test(file.name)) {
|
||||
const sourcemapFile = output.outputFiles.filter(f => /\.js\.map$/.test(f.name))[0];
|
||||
if (sourcemapFile) {
|
||||
const extname = path.extname(vinyl.relative);
|
||||
const basename = path.basename(vinyl.relative, extname);
|
||||
const dirname = path.dirname(vinyl.relative);
|
||||
const tsname = (dirname === '.' ? '' : dirname + '/') + basename + '.ts';
|
||||
const sourceMap = JSON.parse(sourcemapFile.text);
|
||||
sourceMap.sources[0] = tsname.replace(/\\/g, '/');
|
||||
vinyl.sourceMap = sourceMap;
|
||||
}
|
||||
}
|
||||
files.push(vinyl);
|
||||
}
|
||||
resolve({
|
||||
fileName,
|
||||
signature,
|
||||
files
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
const newErrors = Object.create(null);
|
||||
const t1 = Date.now();
|
||||
const toBeEmitted = [];
|
||||
const toBeCheckedSyntactically = [];
|
||||
const toBeCheckedSemantically = [];
|
||||
const filesWithChangedSignature = [];
|
||||
const dependentFiles = [];
|
||||
const newLastBuildVersion = new Map();
|
||||
for (const fileName of host.getScriptFileNames()) {
|
||||
if (lastBuildVersion[fileName] !== host.getScriptVersion(fileName)) {
|
||||
toBeEmitted.push(fileName);
|
||||
toBeCheckedSyntactically.push(fileName);
|
||||
toBeCheckedSemantically.push(fileName);
|
||||
}
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
const semanticCheckInfo = new Map();
|
||||
const seenAsDependentFile = new Set();
|
||||
function workOnNext() {
|
||||
let promise;
|
||||
// let fileName: string;
|
||||
// someone told us to stop this
|
||||
if (token.isCancellationRequested()) {
|
||||
_log('[CANCEL]', '>>This compile run was cancelled<<');
|
||||
newLastBuildVersion.clear();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
// (1st) emit code
|
||||
else if (toBeEmitted.length) {
|
||||
const fileName = toBeEmitted.pop();
|
||||
promise = emitSoon(fileName).then(value => {
|
||||
for (const file of value.files) {
|
||||
_log('[emit code]', file.path);
|
||||
out(file);
|
||||
}
|
||||
// remember when this was build
|
||||
newLastBuildVersion.set(fileName, host.getScriptVersion(fileName));
|
||||
// remeber the signature
|
||||
if (value.signature && lastDtsHash[fileName] !== value.signature) {
|
||||
lastDtsHash[fileName] = value.signature;
|
||||
filesWithChangedSignature.push(fileName);
|
||||
}
|
||||
}).catch(e => {
|
||||
// can't just skip this or make a result up..
|
||||
host.error(`ERROR emitting ${fileName}`);
|
||||
host.error(e);
|
||||
});
|
||||
}
|
||||
// (2nd) check syntax
|
||||
else if (toBeCheckedSyntactically.length) {
|
||||
const fileName = toBeCheckedSyntactically.pop();
|
||||
_log('[check syntax]', fileName);
|
||||
promise = checkSyntaxSoon(fileName).then(diagnostics => {
|
||||
delete oldErrors[fileName];
|
||||
if (diagnostics.length > 0) {
|
||||
diagnostics.forEach(d => onError(d));
|
||||
newErrors[fileName] = diagnostics;
|
||||
// stop the world when there are syntax errors
|
||||
toBeCheckedSyntactically.length = 0;
|
||||
toBeCheckedSemantically.length = 0;
|
||||
filesWithChangedSignature.length = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
// (3rd) check semantics
|
||||
else if (toBeCheckedSemantically.length) {
|
||||
let fileName = toBeCheckedSemantically.pop();
|
||||
while (fileName && semanticCheckInfo.has(fileName)) {
|
||||
fileName = toBeCheckedSemantically.pop();
|
||||
}
|
||||
if (fileName) {
|
||||
_log('[check semantics]', fileName);
|
||||
promise = checkSemanticsSoon(fileName).then(diagnostics => {
|
||||
delete oldErrors[fileName];
|
||||
semanticCheckInfo.set(fileName, diagnostics.length);
|
||||
if (diagnostics.length > 0) {
|
||||
diagnostics.forEach(d => onError(d));
|
||||
newErrors[fileName] = diagnostics;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// (4th) check dependents
|
||||
else if (filesWithChangedSignature.length) {
|
||||
while (filesWithChangedSignature.length) {
|
||||
const fileName = filesWithChangedSignature.pop();
|
||||
if (!isExternalModule(service.getProgram().getSourceFile(fileName))) {
|
||||
_log('[check semantics*]', fileName + ' is an internal module and it has changed shape -> check whatever hasn\'t been checked yet');
|
||||
toBeCheckedSemantically.push(...host.getScriptFileNames());
|
||||
filesWithChangedSignature.length = 0;
|
||||
dependentFiles.length = 0;
|
||||
break;
|
||||
}
|
||||
host.collectDependents(fileName, dependentFiles);
|
||||
}
|
||||
}
|
||||
// (5th) dependents contd
|
||||
else if (dependentFiles.length) {
|
||||
let fileName = dependentFiles.pop();
|
||||
while (fileName && seenAsDependentFile.has(fileName)) {
|
||||
fileName = dependentFiles.pop();
|
||||
}
|
||||
if (fileName) {
|
||||
seenAsDependentFile.add(fileName);
|
||||
const value = semanticCheckInfo.get(fileName);
|
||||
if (value === 0) {
|
||||
// already validated successfully -> look at dependents next
|
||||
host.collectDependents(fileName, dependentFiles);
|
||||
}
|
||||
else if (typeof value === 'undefined') {
|
||||
// first validate -> look at dependents next
|
||||
dependentFiles.push(fileName);
|
||||
toBeCheckedSemantically.push(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
// (last) done
|
||||
else {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
if (!promise) {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
promise.then(function () {
|
||||
// change to change
|
||||
process.nextTick(workOnNext);
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
workOnNext();
|
||||
}).then(() => {
|
||||
// store the build versions to not rebuilt the next time
|
||||
newLastBuildVersion.forEach((value, key) => {
|
||||
lastBuildVersion[key] = value;
|
||||
});
|
||||
// print old errors and keep them
|
||||
utils.collections.forEach(oldErrors, entry => {
|
||||
entry.value.forEach(diag => onError(diag));
|
||||
newErrors[entry.key] = entry.value;
|
||||
});
|
||||
oldErrors = newErrors;
|
||||
// print stats
|
||||
const headNow = process.memoryUsage().heapUsed;
|
||||
const MB = 1024 * 1024;
|
||||
_log('[tsb]', `time: ${colors.yellow((Date.now() - t1) + 'ms')} + \nmem: ${colors.cyan(Math.ceil(headNow / MB) + 'MB')} ${colors.bgCyan('delta: ' + Math.ceil((headNow - headUsed) / MB))}`);
|
||||
headUsed = headNow;
|
||||
});
|
||||
}
|
||||
return {
|
||||
file,
|
||||
build,
|
||||
languageService: service
|
||||
};
|
||||
}
|
||||
exports.createTypeScriptBuilder = createTypeScriptBuilder;
|
||||
class ScriptSnapshot {
|
||||
constructor(text, mtime) {
|
||||
this._text = text;
|
||||
this._mtime = mtime;
|
||||
}
|
||||
getVersion() {
|
||||
return this._mtime.toUTCString();
|
||||
}
|
||||
getText(start, end) {
|
||||
return this._text.substring(start, end);
|
||||
}
|
||||
getLength() {
|
||||
return this._text.length;
|
||||
}
|
||||
getChangeRange(_oldSnapshot) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
class VinylScriptSnapshot extends ScriptSnapshot {
|
||||
constructor(file) {
|
||||
super(file.contents.toString(), file.stat.mtime);
|
||||
this._base = file.base;
|
||||
}
|
||||
getBase() {
|
||||
return this._base;
|
||||
}
|
||||
}
|
||||
class LanguageServiceHost {
|
||||
constructor(_cmdLine, _projectPath, _log) {
|
||||
this._cmdLine = _cmdLine;
|
||||
this._projectPath = _projectPath;
|
||||
this._log = _log;
|
||||
this.directoryExists = ts.sys.directoryExists;
|
||||
this.getDirectories = ts.sys.getDirectories;
|
||||
this.fileExists = ts.sys.fileExists;
|
||||
this.readFile = ts.sys.readFile;
|
||||
this.readDirectory = ts.sys.readDirectory;
|
||||
this._snapshots = Object.create(null);
|
||||
this._filesInProject = new Set(_cmdLine.fileNames);
|
||||
this._filesAdded = new Set();
|
||||
this._dependencies = new utils.graph.Graph(s => s);
|
||||
this._dependenciesRecomputeList = [];
|
||||
this._fileNameToDeclaredModule = Object.create(null);
|
||||
this._projectVersion = 1;
|
||||
}
|
||||
log(_s) {
|
||||
// console.log(s);
|
||||
}
|
||||
trace(_s) {
|
||||
// console.log(s);
|
||||
}
|
||||
error(s) {
|
||||
console.error(s);
|
||||
}
|
||||
getCompilationSettings() {
|
||||
return this._cmdLine.options;
|
||||
}
|
||||
getProjectVersion() {
|
||||
return String(this._projectVersion);
|
||||
}
|
||||
getScriptFileNames() {
|
||||
const res = Object.keys(this._snapshots).filter(path => this._filesInProject.has(path) || this._filesAdded.has(path));
|
||||
return res;
|
||||
}
|
||||
getScriptVersion(filename) {
|
||||
filename = normalize(filename);
|
||||
const result = this._snapshots[filename];
|
||||
if (result) {
|
||||
return result.getVersion();
|
||||
}
|
||||
return 'UNKNWON_FILE_' + Math.random().toString(16).slice(2);
|
||||
}
|
||||
getScriptSnapshot(filename, resolve = true) {
|
||||
filename = normalize(filename);
|
||||
let result = this._snapshots[filename];
|
||||
if (!result && resolve) {
|
||||
try {
|
||||
result = new VinylScriptSnapshot(new Vinyl({
|
||||
path: filename,
|
||||
contents: (0, fs_1.readFileSync)(filename),
|
||||
base: this.getCompilationSettings().outDir,
|
||||
stat: (0, fs_1.statSync)(filename)
|
||||
}));
|
||||
this.addScriptSnapshot(filename, result);
|
||||
}
|
||||
catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
addScriptSnapshot(filename, snapshot) {
|
||||
this._projectVersion++;
|
||||
filename = normalize(filename);
|
||||
const old = this._snapshots[filename];
|
||||
if (!old && !this._filesInProject.has(filename) && !filename.endsWith('.d.ts')) {
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// not very proper!
|
||||
this._filesAdded.add(filename);
|
||||
}
|
||||
if (!old || old.getVersion() !== snapshot.getVersion()) {
|
||||
this._dependenciesRecomputeList.push(filename);
|
||||
const node = this._dependencies.lookup(filename);
|
||||
if (node) {
|
||||
node.outgoing = Object.create(null);
|
||||
}
|
||||
// (cheap) check for declare module
|
||||
LanguageServiceHost._declareModule.lastIndex = 0;
|
||||
let match;
|
||||
while ((match = LanguageServiceHost._declareModule.exec(snapshot.getText(0, snapshot.getLength())))) {
|
||||
let declaredModules = this._fileNameToDeclaredModule[filename];
|
||||
if (!declaredModules) {
|
||||
this._fileNameToDeclaredModule[filename] = declaredModules = [];
|
||||
}
|
||||
declaredModules.push(match[2]);
|
||||
}
|
||||
}
|
||||
this._snapshots[filename] = snapshot;
|
||||
return old;
|
||||
}
|
||||
removeScriptSnapshot(filename) {
|
||||
this._filesInProject.delete(filename);
|
||||
this._filesAdded.delete(filename);
|
||||
this._projectVersion++;
|
||||
filename = normalize(filename);
|
||||
delete this._fileNameToDeclaredModule[filename];
|
||||
return delete this._snapshots[filename];
|
||||
}
|
||||
getCurrentDirectory() {
|
||||
return path.dirname(this._projectPath);
|
||||
}
|
||||
getDefaultLibFileName(options) {
|
||||
return ts.getDefaultLibFilePath(options);
|
||||
}
|
||||
// ---- dependency management
|
||||
collectDependents(filename, target) {
|
||||
while (this._dependenciesRecomputeList.length) {
|
||||
this._processFile(this._dependenciesRecomputeList.pop());
|
||||
}
|
||||
filename = normalize(filename);
|
||||
const node = this._dependencies.lookup(filename);
|
||||
if (node) {
|
||||
utils.collections.forEach(node.incoming, entry => target.push(entry.key));
|
||||
}
|
||||
}
|
||||
_processFile(filename) {
|
||||
if (filename.match(/.*\.d\.ts$/)) {
|
||||
return;
|
||||
}
|
||||
filename = normalize(filename);
|
||||
const snapshot = this.getScriptSnapshot(filename);
|
||||
if (!snapshot) {
|
||||
this._log('processFile', `Missing snapshot for: ${filename}`);
|
||||
return;
|
||||
}
|
||||
const info = ts.preProcessFile(snapshot.getText(0, snapshot.getLength()), true);
|
||||
// (1) ///-references
|
||||
info.referencedFiles.forEach(ref => {
|
||||
const resolvedPath = path.resolve(path.dirname(filename), ref.fileName);
|
||||
const normalizedPath = normalize(resolvedPath);
|
||||
this._dependencies.inertEdge(filename, normalizedPath);
|
||||
});
|
||||
// (2) import-require statements
|
||||
info.importedFiles.forEach(ref => {
|
||||
const stopDirname = normalize(this.getCurrentDirectory());
|
||||
let dirname = filename;
|
||||
let found = false;
|
||||
while (!found && dirname.indexOf(stopDirname) === 0) {
|
||||
dirname = path.dirname(dirname);
|
||||
const resolvedPath = path.resolve(dirname, ref.fileName);
|
||||
const normalizedPath = normalize(resolvedPath);
|
||||
if (this.getScriptSnapshot(normalizedPath + '.ts')) {
|
||||
this._dependencies.inertEdge(filename, normalizedPath + '.ts');
|
||||
found = true;
|
||||
}
|
||||
else if (this.getScriptSnapshot(normalizedPath + '.d.ts')) {
|
||||
this._dependencies.inertEdge(filename, normalizedPath + '.d.ts');
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
for (const key in this._fileNameToDeclaredModule) {
|
||||
if (this._fileNameToDeclaredModule[key] && ~this._fileNameToDeclaredModule[key].indexOf(ref.fileName)) {
|
||||
this._dependencies.inertEdge(filename, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
LanguageServiceHost._declareModule = /declare\s+module\s+('|")(.+)\1/g;
|
||||
Reference in New Issue
Block a user