diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md index 5711c8f950d..89da312503a 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -16,7 +16,7 @@ Please fill in the *entire* template below. --> -**TypeScript Version:** 3.3.0-dev.201xxxxx +**TypeScript Version:** 3.4.0-dev.201xxxxx **Search Terms:** diff --git a/.mailmap b/.mailmap index 5b591eb4615..cb3334a9404 100644 --- a/.mailmap +++ b/.mailmap @@ -121,6 +121,7 @@ Ken Howard Kevin Lang kimamula # Kenji Imamula Kitson Kelly +Krishnadas Babu Klaus Meinhardt Kyle Kelley Lorant Pinter diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 683c47817b7..980f84a3800 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,6 +47,16 @@ In general, things we find useful when reviewing suggestions are: # Instructions for Contributing Code +## Tips + +### Faster clones + +The TypeScript repository is relatively large. To save some time, you might want to clone it without the repo's full history using `git clone --depth=1`. + +### Using local builds + +Run `gulp build` to build a version of the compiler/language service that reflects changes you've made. You can then run `node /built/local/tsc.js` in place of `tsc` in your project. For example, to run `tsc --watch` from within the root of the repository on a file called `test.ts`, you can run `node ./built/local/tsc.js --watch test.ts`. + ## Contributing bug fixes TypeScript is currently accepting contributions in the form of bug fixes. A bug must have an issue tracking it in the issue tracker that has been approved ("Milestone == Community") by the TypeScript team. Your pull request should include a link to the bug that you are fixing. If you've submitted a PR for a bug, please post a comment in the bug to avoid duplication of effort. diff --git a/Gulpfile.js b/Gulpfile.js index df8a832426d..68a2b79f222 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -1,107 +1,70 @@ -/// // @ts-check const path = require("path"); -const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util) const fs = require("fs"); -const child_process = require("child_process"); -const runSequence = require("run-sequence"); +const log = require("fancy-log"); const newer = require("gulp-newer"); -const insert = require("gulp-insert"); -const { append } = require("gulp-insert"); const sourcemaps = require("gulp-sourcemaps"); const del = require("del"); const fold = require("travis-fold"); const rename = require("gulp-rename"); -const mkdirp = require("./scripts/build/mkdirp"); -const gulp = require("./scripts/build/gulp"); -const getDirSize = require("./scripts/build/getDirSize"); -const project = require("./scripts/build/project"); -const replace = require("./scripts/build/replace"); -const convertConstEnums = require("./scripts/build/convertConstEnum"); -const needsUpdate = require("./scripts/build/needsUpdate"); -const getDiffTool = require("./scripts/build/getDiffTool"); -const baselineAccept = require("./scripts/build/baselineAccept"); -const cmdLineOptions = require("./scripts/build/options"); -const exec = require("./scripts/build/exec"); -const browserify = require("./scripts/build/browserify"); -const prepend = require("./scripts/build/prepend"); -const { removeSourceMaps } = require("./scripts/build/sourcemaps"); -const { CancellationTokenSource, CancelError, delay, Semaphore } = require("prex"); -const { libraryTargets, generateLibs } = require("./scripts/build/lib"); +const concat = require("gulp-concat"); +const merge2 = require("merge2"); +const mkdirp = require("mkdirp"); +const { src, dest, task, parallel, series, watch } = require("gulp"); +const { append, transform } = require("gulp-insert"); +const { browserify } = require("./scripts/build/browserify"); +const { prependFile } = require("./scripts/build/prepend"); +const { exec, readJson, needsUpdate, getDiffTool, getDirSize, flatten, rm } = require("./scripts/build/utils"); const { runConsoleTests, cleanTestDirs, writeTestConfigFile, refBaseline, localBaseline, refRwcBaseline, localRwcBaseline } = require("./scripts/build/tests"); +const { buildProject, cleanProject, watchProject } = require("./scripts/build/projects"); +const cmdLineOptions = require("./scripts/build/options"); -Error.stackTraceLimit = 1000; - -// Constants -const host = cmdLineOptions.host; const copyright = "CopyrightNotice.txt"; +const cleanTasks = []; -project.addTypeScript("lkg", "./lib/typescript.js"); -project.addTypeScript("built", "./built/local/typescriptServices.js"); -project.addTypeScript("default", "lkg"); // Compile using the LKG compiler by default +const buildScripts = () => buildProject("scripts"); +const cleanScripts = () => cleanProject("scripts"); +cleanTasks.push(cleanScripts); -const scriptsProject = "scripts/tsconfig.json"; -const configurePrereleaseJs = "scripts/configurePrerelease.js"; -const processDiagnosticMessagesJs = "scripts/processDiagnosticMessages.js"; -const generateLocalizedDiagnosticMessagesJs = "scripts/generateLocalizedDiagnosticMessages.js"; -const buildProtocolJs = "scripts/buildProtocol.js"; -const produceLKGJs = "scripts/produceLKG.js"; -const word2mdJs = "scripts/word2md.js"; -const scriptsTaskAliases = [configurePrereleaseJs, processDiagnosticMessagesJs, generateLocalizedDiagnosticMessagesJs, produceLKGJs, buildProtocolJs, word2mdJs]; -gulp.task("scripts", /*help*/ false, () => project.compile(scriptsProject), { aliases: scriptsTaskAliases }); -gulp.task("clean:scripts", /*help*/ false, () => project.clean(scriptsProject), { aliases: scriptsTaskAliases.map(alias => `clean:${alias}`)}); +const libraries = readJson("./src/lib/libs.json"); +const libs = libraries.libs.map(lib => { + const relativeSources = ["header.d.ts"].concat(libraries.sources && libraries.sources[lib] || [lib + ".d.ts"]); + const relativeTarget = libraries.paths && libraries.paths[lib] || ("lib." + lib + ".d.ts"); + const sources = relativeSources.map(s => path.posix.join("src/lib", s)); + const target = `built/local/${relativeTarget}`; + return { target, relativeTarget, sources }; +}); -// Nightly management tasks -gulp.task( - "configure-nightly", - "Runs scripts/configurePrerelease.ts to prepare a build for nightly publishing", - [configurePrereleaseJs], - () => exec(host, [configurePrereleaseJs, "dev", "package.json", "src/compiler/core.ts"])); +const generateLibs = () => { + return merge2(libs.map(({ sources, target, relativeTarget }) => + src([copyright, ...sources]) + .pipe(newer(target)) + .pipe(concat(relativeTarget, { newLine: "\n\n" })) + .pipe(dest("built/local")))); +}; +task("lib", generateLibs) +task("lib").description = "Builds the library targets"; -gulp.task( - "publish-nightly", - "Runs `npm publish --tag next` to create a new nightly build on npm", - ["LKG"], - () => runSequence("clean", "runtests-parallel", - () => exec("npm", ["publish", "--tag", "next"]))); +const cleanLib = () => del(libs.map(lib => lib.target)); +cleanTasks.push(cleanLib); -const importDefinitelyTypedTestsProject = "scripts/importDefinitelyTypedTests/tsconfig.json"; -const importDefinitelyTypedTestsJs = "scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js"; -gulp.task(importDefinitelyTypedTestsJs, /*help*/ false, () => project.compile(importDefinitelyTypedTestsProject)); -gulp.task(`clean:${importDefinitelyTypedTestsJs}`, /*help*/ false, () => project.clean(importDefinitelyTypedTestsProject)); +const watchLib = () => watch(["src/lib/**/*"], generateLibs); -gulp.task( - "importDefinitelyTypedTests", - "Runs scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.ts to copy DT's tests to the TS-internal RWC tests", - [importDefinitelyTypedTestsJs], - () => exec(host, [importDefinitelyTypedTestsJs, "./", "../DefinitelyTyped"])); - -gulp.task( - "lib", - "Builds the library targets", - () => generateLibs([copyright])); - -// The generated diagnostics map; built for the compiler and for the "generate-diagnostics" task const diagnosticInformationMapTs = "src/compiler/diagnosticInformationMap.generated.ts"; const diagnosticMessagesJson = "src/compiler/diagnosticMessages.json"; const diagnosticMessagesGeneratedJson = "src/compiler/diagnosticMessages.generated.json"; -gulp.task(diagnosticInformationMapTs, /*help*/ false, [processDiagnosticMessagesJs], () => { +const generateDiagnostics = async () => { if (needsUpdate(diagnosticMessagesJson, [diagnosticMessagesGeneratedJson, diagnosticInformationMapTs])) { - return exec(host, [processDiagnosticMessagesJs, diagnosticMessagesJson]); + await exec(process.execPath, ["scripts/processDiagnosticMessages.js", diagnosticMessagesJson]); } -}); -gulp.task(`clean:${diagnosticInformationMapTs}`, /*help*/ false, () => del([diagnosticInformationMapTs, diagnosticMessagesGeneratedJson])); +}; +task("generate-diagnostics", series(buildScripts, generateDiagnostics)); +task("generate-diagnostics").description = "Generates a diagnostic file in TypeScript based on an input JSON file"; -const builtGeneratedDiagnosticMessagesJson = "built/local/diagnosticMessages.generated.json"; -gulp.task(builtGeneratedDiagnosticMessagesJson, /*help*/ false, [diagnosticInformationMapTs], () => - gulp.src([diagnosticMessagesGeneratedJson], { base: "src/compiler" }) - .pipe(newer(builtGeneratedDiagnosticMessagesJson)) - .pipe(gulp.dest("built/local"))); +const cleanDiagnostics = () => del([diagnosticInformationMapTs, diagnosticMessagesGeneratedJson]); +cleanTasks.push(cleanDiagnostics); -gulp.task( - "generate-diagnostics", - "Generates a diagnostic file in TypeScript based on an input JSON file", - [diagnosticInformationMapTs]); +const watchDiagnostics = () => watch(["src/compiler/diagnosticMessages.json"], task("generate-diagnostics")); // Localize diagnostics /** @@ -122,178 +85,508 @@ const localizationTargets = ["cs", "de", "es", "fr", "it", "ja", "ko", "pl", "pt .map(f => `built/local/${f}/diagnosticMessages.generated.json`) .concat(generatedLCGFile); -gulp.task(generatedLCGFile, /*help*/ false, [generateLocalizedDiagnosticMessagesJs, diagnosticInformationMapTs], (done) => { +const localize = async () => { if (needsUpdate(diagnosticMessagesGeneratedJson, generatedLCGFile)) { - return exec(host, [generateLocalizedDiagnosticMessagesJs, "src/loc/lcl", "built/local", diagnosticMessagesGeneratedJson], { ignoreExitCode: true }); + return exec(process.execPath, ["scripts/generateLocalizedDiagnosticMessages.js", "src/loc/lcl", "built/local", diagnosticMessagesGeneratedJson], { ignoreExitCode: true }); } -}); +}; -gulp.task("localize", /*help*/ false, [generatedLCGFile]); +// Pre-build steps when targeting the LKG compiler +const lkgPreBuild = parallel(generateLibs, series(buildScripts, generateDiagnostics)); -const servicesProject = "src/services/tsconfig.json"; -const typescriptServicesProject = "built/local/typescriptServices.tsconfig.json"; -gulp.task(typescriptServicesProject, /*help*/ false, () => { - // NOTE: flatten services so that we can properly strip @internal - project.flatten(servicesProject, typescriptServicesProject, { +const buildTsc = () => buildProject("src/tsc"); +task("tsc", series(lkgPreBuild, buildTsc)); +task("tsc").description = "Builds the command-line compiler"; + +const cleanTsc = () => cleanProject("src/tsc"); +cleanTasks.push(cleanTsc); +task("clean-tsc", cleanTsc); +task("clean-tsc").description = "Cleans outputs for the command-line compiler"; + +const watchTsc = () => watchProject("src/tsc"); +task("watch-tsc", series(lkgPreBuild, parallel(watchLib, watchDiagnostics, watchTsc))); +task("watch-tsc").description = "Watch for changes and rebuild the command-line compiler only."; + +// Pre-build steps when targeting the built/local compiler. +const localPreBuild = parallel(generateLibs, series(buildScripts, generateDiagnostics, buildTsc)); + +// Pre-build steps to use based on supplied options. +const preBuild = cmdLineOptions.lkg ? lkgPreBuild : localPreBuild; + +const buildServices = (() => { + // flatten the services project + const flattenServicesConfig = async () => flatten("src/services/tsconfig.json", "built/local/typescriptServices.tsconfig.json", { compilerOptions: { "removeComments": false, "stripInternal": true, "declarationMap": false, - "outFile": "typescriptServices.out.js" // must align with same task in jakefile. We fix this name below. + "outFile": "typescriptServices.out.js" } }); -}); -const typescriptServicesJs = "built/local/typescriptServices.js"; -const typescriptServicesDts = "built/local/typescriptServices.d.ts"; -gulp.task(typescriptServicesJs, /*help*/ false, ["lib", "generate-diagnostics", typescriptServicesProject], () => - project.compile(typescriptServicesProject, { - js: files => files - .pipe(prepend.file(copyright)) - .pipe(rename("typescriptServices.js")), - dts: files => files - .pipe(removeSourceMaps()) - .pipe(prepend.file(copyright)) - .pipe(convertConstEnums()) - .pipe(rename("typescriptServices.d.ts")) - }), - { aliases: [typescriptServicesDts] }); + // build typescriptServices.out.js + const buildTypescriptServicesOut = () => buildProject("built/local/typescriptServices.tsconfig.json", cmdLineOptions); -const typescriptJs = "built/local/typescript.js"; -gulp.task(typescriptJs, /*help*/ false, [typescriptServicesJs], () => - gulp.src([typescriptServicesJs], { base: "built/local" }) - .pipe(newer(typescriptJs)) + // create typescriptServices.js + const createTypescriptServicesJs = () => src("built/local/typescriptServices.out.js") + .pipe(newer("built/local/typescriptServices.js")) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(prependFile(copyright)) + .pipe(rename("typescriptServices.js")) + .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) + .pipe(dest("built/local")); + + // create typescriptServices.d.ts + const createTypescriptServicesDts = () => src("built/local/typescriptServices.out.d.ts") + .pipe(newer("built/local/typescriptServices.d.ts")) + .pipe(prependFile(copyright)) + .pipe(transform(content => content.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4"))) + .pipe(rename("typescriptServices.d.ts")) + .pipe(dest("built/local")); + + // create typescript.js + const createTypescriptJs = () => src("built/local/typescriptServices.js") + .pipe(newer("built/local/typescript.js")) + .pipe(sourcemaps.init({ loadMaps: true })) .pipe(rename("typescript.js")) - .pipe(gulp.dest("built/local"))) + .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) + .pipe(dest("built/local")); -const typescriptDts = "built/local/typescript.d.ts"; -gulp.task(typescriptDts, /*help*/ false, [typescriptServicesDts], () => - gulp.src([typescriptServicesDts], { base: "built/local" }) - .pipe(newer(typescriptDts)) + // create typescript.d.ts + const createTypescriptDts = () => src("built/local/typescriptServices.d.ts") + .pipe(newer("built/local/typescript.d.ts")) .pipe(append("\nexport = ts;")) .pipe(rename("typescript.d.ts")) - .pipe(gulp.dest("built/local"))); + .pipe(dest("built/local")); -const typescriptStandaloneDts = "built/local/typescript_standalone.d.ts"; -gulp.task(typescriptStandaloneDts, /*help*/ false, [typescriptServicesDts], () => - gulp.src([typescriptServicesDts], { base: "built/local" }) - .pipe(newer(typescriptStandaloneDts)) - .pipe(replace(/declare (namespace|module) ts/g, 'declare module "typescript"')) + // create typescript_standalone.d.ts + const createTypescriptStandaloneDts = () => src("built/local/typescriptServices.d.ts") + .pipe(newer("built/local/typescript_standalone.d.ts")) + .pipe(transform(content => content.replace(/declare (namespace|module) ts/g, 'declare module "typescript"'))) .pipe(rename("typescript_standalone.d.ts")) - .pipe(gulp.dest("built/local"))); + .pipe(dest("built/local")); -// build all 'typescriptServices'-related outputs -gulp.task("services", /*help*/ false, [typescriptServicesJs, typescriptServicesDts, typescriptJs, typescriptDts, typescriptStandaloneDts]); + return series( + flattenServicesConfig, + buildTypescriptServicesOut, + createTypescriptServicesJs, + createTypescriptServicesDts, + createTypescriptJs, + createTypescriptDts, + createTypescriptStandaloneDts); +})(); +task("services", series(preBuild, buildServices)); +task("services").description = "Builds the language service"; +task("services").flags = { + " --built": "Compile using the built version of the compiler." +} -const useCompiler = cmdLineOptions.lkg ? "lkg" : "built"; -const useCompilerDeps = cmdLineOptions.lkg ? ["lib", "generate-diagnostics"] : [typescriptServicesJs]; +const cleanServices = async () => { + if (fs.existsSync("built/local/typescriptServices.tsconfig.json")) { + await cleanProject("built/local/typescriptServices.tsconfig.json"); + } + await del([ + "built/local/typescriptServices.tsconfig.json", + "built/local/typescriptServices.out.js", + "built/local/typescriptServices.out.d.ts", + "built/local/typescriptServices.js", + "built/local/typescript.js", + "built/local/typescript.d.ts", + "built/local/typescript_standalone.d.ts", + ]); +}; +cleanTasks.push(cleanServices); +task("clean-services", cleanServices); +task("clean-services").description = "Cleans outputs for the language service"; -const tscProject = "src/tsc/tsconfig.json"; -const tscJs = "built/local/tsc.js"; -gulp.task(tscJs, /*help*/ false, useCompilerDeps, () => - project.compile(tscProject, { - typescript: useCompiler, - js: files => files.pipe(prepend.file(copyright)) - })); +const watchServices = () => watch([ + "src/compiler/tsconfig.json", + "src/compiler/**/*.ts", + "src/jsTyping/tsconfig.json", + "src/jsTyping/**/*.ts", + "src/services/tsconfig.json", + "src/services/**/*.ts", +], series(preBuild, buildServices)); +task("watch-services", series(preBuild, parallel(watchLib, watchDiagnostics, watchServices))); +task("watch-services").description = "Watches for changes and rebuild language service only"; +task("watch-services").flags = { + " --built": "Compile using the built version of the compiler." +} -const tscReleaseProject = "src/tsc/tsconfig.release.json"; -const tscReleaseJs = "built/local/tsc.release.js"; -gulp.task(tscReleaseJs, /*help*/ false, () => - project.compile(tscReleaseProject, { - js: files => files.pipe(prepend.file(copyright)) - })); +const buildServer = () => buildProject("src/tsserver", cmdLineOptions); +task("tsserver", series(preBuild, buildServer)); +task("tsserver").description = "Builds the language server"; +task("tsserver").flags = { + " --built": "Compile using the built version of the compiler." +} -const cancellationTokenProject = "src/cancellationToken/tsconfig.json"; -const cancellationTokenJs = "built/local/cancellationToken.js"; -gulp.task(cancellationTokenJs, /*help*/ false, useCompilerDeps, () => project.compile(cancellationTokenProject, { typescript: useCompiler })); +const cleanServer = () => cleanProject("src/tsserver"); +cleanTasks.push(cleanServer); +task("clean-tsserver", cleanServer); +task("clean-tsserver").description = "Cleans outputs for the language server"; -const typingsInstallerProject = "src/typingsInstaller/tsconfig.json"; -const typingsInstallerJs = "built/local/typingsInstaller.js"; -gulp.task(typingsInstallerJs, /*help*/ false, useCompilerDeps, () => project.compile(typingsInstallerProject, { typescript: useCompiler })); +const watchServer = () => watchProject("src/tsserver", cmdLineOptions); +task("watch-tsserver", series(preBuild, parallel(watchLib, watchDiagnostics, watchServer))); +task("watch-tsserver").description = "Watch for changes and rebuild the language server only"; +task("watch-tsserver").flags = { + " --built": "Compile using the built version of the compiler." +} -const tsserverProject = "src/tsserver/tsconfig.json"; -const tsserverJs = "built/local/tsserver.js"; -gulp.task(tsserverJs, /*help*/ false, useCompilerDeps, () => project.compile(tsserverProject, { typescript: useCompiler })); +task("min", series(lkgPreBuild, parallel(buildTsc, buildServer))); +task("min").description = "Builds only tsc and tsserver"; +task("min").flags = { + " --built": "Compile using the built version of the compiler." +} -const watchGuardProject = "src/watchGuard/tsconfig.json"; -const watchGuardJs = "built/local/watchGuard.js"; -gulp.task(watchGuardJs, /*help*/ false, useCompilerDeps, () => project.compile(watchGuardProject, { typescript: useCompiler })); +task("clean-min", series(cleanTsc, cleanServer)); +task("clean-min").description = "Cleans outputs for tsc and tsserver"; -const typesMapJson = "built/local/typesMap.json"; -gulp.task(typesMapJson, /*help*/ false, [], () => - gulp.src("src/server/typesMap.json") - .pipe(newer(typesMapJson)) - .pipe(insert.transform(contents => (JSON.parse(contents), contents))) - .pipe(gulp.dest("built/local"))); +task("watch-min", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServer))); +task("watch-min").description = "Watches for changes to a tsc and tsserver only"; +task("watch-min").flags = { + " --built": "Compile using the built version of the compiler." +} -const tsserverlibraryProject = "built/local/tsserverlibrary.tsconfig.json"; -gulp.task(tsserverlibraryProject, /*help*/ false, () => { - // NOTE: flatten tsserverlibrary so that we can properly strip @internal - project.flatten("src/tsserver/tsconfig.json", tsserverlibraryProject, { +const buildLssl = (() => { + // flatten the server project + const flattenTsServerProject = async () => flatten("src/tsserver/tsconfig.json", "built/local/tsserverlibrary.tsconfig.json", { exclude: ["src/tsserver/server.ts"], compilerOptions: { "removeComments": false, "stripInternal": true, + "declaration": true, "declarationMap": false, - "outFile": "tsserverlibrary.out.js" // must align with same task in jakefile. We fix this name below. + "outFile": "tsserverlibrary.out.js" } }); -}); -const tsserverlibraryJs = "built/local/tsserverlibrary.js"; -const tsserverlibraryDts = "built/local/tsserverlibrary.d.ts"; -gulp.task(tsserverlibraryJs, /*help*/ false, useCompilerDeps.concat([tsserverlibraryProject]), () => - project.compile(tsserverlibraryProject, { - js: files => files - .pipe(prepend.file(copyright)) - .pipe(rename("tsserverlibrary.js")), - dts: files => files - .pipe(removeSourceMaps()) - .pipe(prepend.file(copyright)) - .pipe(convertConstEnums()) - .pipe(append("\nexport = ts;\nexport as namespace ts;")) - .pipe(rename("tsserverlibrary.d.ts")), - typescript: useCompiler - }), { aliases: [tsserverlibraryDts] }); + // build tsserverlibrary.out.js + const buildServerLibraryOut = () => buildProject("built/local/tsserverlibrary.tsconfig.json", cmdLineOptions); -gulp.task( - "lssl", - "Builds language service server library", - [tsserverlibraryDts]); + // create tsserverlibrary.js + const createServerLibraryJs = () => src("built/local/tsserverlibrary.out.js") + .pipe(newer("built/local/tsserverlibrary.js")) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(prependFile(copyright)) + .pipe(rename("tsserverlibrary.js")) + .pipe(sourcemaps.write(".", { includeContent: false, destPath: "built/local" })) + .pipe(dest("built/local")); -gulp.task( - "local", - "Builds the full compiler and services", - [tscJs, "services", tsserverJs, builtGeneratedDiagnosticMessagesJson, tsserverlibraryDts, "localize"]); + // create tsserverlibrary.d.ts + const createServerLibraryDts = () => src("built/local/tsserverlibrary.out.d.ts") + .pipe(newer("built/local/tsserverlibrary.d.ts")) + .pipe(prependFile(copyright)) + .pipe(transform(content => content.replace(/^(\s*)(export )?const enum (\S+) {(\s*)$/gm, "$1$2enum $3 {$4"))) + .pipe(append("\nexport = ts;\nexport as namespace ts;")) + .pipe(rename("tsserverlibrary.d.ts")) + .pipe(dest("built/local")); -gulp.task( - "tsc", - "Builds only the compiler", - [tscJs]); + return series( + flattenTsServerProject, + buildServerLibraryOut, + createServerLibraryJs, + createServerLibraryDts); +})(); +task("lssl", series(preBuild, buildLssl)); +task("lssl").description = "Builds language service server library"; +task("lssl").flags = { + " --built": "Compile using the built version of the compiler." +} -// Generate Markdown spec -const specMd = "doc/spec.md"; -gulp.task(specMd, /*help*/ false, [word2mdJs], () => - exec("cscript", ["//nologo", word2mdJs, path.resolve(specMd), path.resolve("doc/TypeScript Language Specification.docx")])); +const cleanLssl = async () => { + if (fs.existsSync("built/local/tsserverlibrary.tsconfig.json")) { + await cleanProject("built/local/tsserverlibrary.tsconfig.json"); + } + await del([ + "built/local/tsserverlibrary.tsconfig.json", + "built/local/tsserverlibrary.out.js", + "built/local/tsserverlibrary.out.d.ts", + "built/local/tsserverlibrary.js", + "built/local/tsserverlibrary.d.ts", + ]); +}; +cleanTasks.push(cleanLssl); +task("clean-lssl", cleanLssl); +task("clean-lssl").description = "Clean outputs for the language service server library"; -gulp.task( - "generate-spec", - "Generates a Markdown version of the Language Specification", - [specMd]); +const watchLssl = () => watch([ + "src/compiler/tsconfig.json", + "src/compiler/**/*.ts", + "src/jsTyping/tsconfig.json", + "src/jsTyping/**/*.ts", + "src/services/tsconfig.json", + "src/services/**/*.ts", + "src/server/tsconfig.json", + "src/server/**/*.ts", + "src/tsserver/tsconfig.json", + "src/tsserver/**/*.ts", +], buildLssl); +task("watch-lssl", series(preBuild, parallel(watchLib, watchDiagnostics, watchLssl))); +task("watch-lssl").description = "Watch for changes and rebuild tsserverlibrary only"; +task("watch-lssl").flags = { + " --built": "Compile using the built version of the compiler." +} -gulp.task("produce-LKG", /*help*/ false, ["scripts", "local", cancellationTokenJs, typingsInstallerJs, watchGuardJs, tscReleaseJs], () => { +const buildTests = () => buildProject("src/testRunner"); +task("tests", series(preBuild, parallel(buildLssl, buildTests))); +task("tests").description = "Builds the test infrastructure"; +task("tests").flags = { + " --built": "Compile using the built version of the compiler." +} + +const cleanTests = () => cleanProject("src/testRunner"); +cleanTasks.push(cleanTests); +task("clean-tests", cleanTests); +task("clean-tests").description = "Cleans the outputs for the test infrastructure"; + +const watchTests = () => watchProject("src/testRunner", cmdLineOptions); + +const buildRules = () => buildProject("scripts/tslint"); +task("build-rules", buildRules); +task("build-rules").description = "Compiles tslint rules to js"; + +const cleanRules = () => cleanProject("scripts/tslint"); +cleanTasks.push(cleanRules); +task("clean-rules", cleanRules); +task("clean-rules").description = "Cleans the outputs for the lint rules"; + +const lintFoldStart = async () => { if (fold.isTravis()) console.log(fold.start("lint")); }; +const lintFoldEnd = async () => { if (fold.isTravis()) console.log(fold.end("lint")); }; +const lint = series([ + lintFoldStart, + ...["scripts/tslint/tsconfig.json", "src/tsconfig-base.json"].map(project => { + const lintOne = () => { + const args = ["node_modules/tslint/bin/tslint", "--project", project, "--formatters-dir", "./built/local/tslint/formatters", "--format", "autolinkableStylish"]; + if (cmdLineOptions.fix) args.push("--fix"); + log(`Linting: node ${args.join(" ")}`); + return exec(process.execPath, args); + }; + lintOne.dispayName = `lint(${project})`; + return lintOne; + }), + lintFoldEnd +]); +lint.displayName = "lint"; +task("lint", series(buildRules, lint)); +task("lint").description = "Runs tslint on the compiler sources."; +task("lint").flags = { + " --f[iles]=": "pattern to match files to lint", +}; + +const buildFoldStart = async () => { if (fold.isTravis()) console.log(fold.start("build")); }; +const buildFoldEnd = async () => { if (fold.isTravis()) console.log(fold.end("build")); }; +task("local", series(buildFoldStart, lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl), buildFoldEnd)); +task("local").description = "Builds the full compiler and services"; +task("local").flags = { + " --built": "Compile using the built version of the compiler." +} + +task("watch-local", series(preBuild, parallel(watchLib, watchDiagnostics, watchTsc, watchServices, watchServer, watchLssl))); +task("watch-local").description = "Watches for changes to projects in src/ (but does not execute tests)."; +task("watch-local").flags = { + " --built": "Compile using the built version of the compiler." +} + +const generateCodeCoverage = () => exec("istanbul", ["cover", "node_modules/mocha/bin/_mocha", "--", "-R", "min", "-t", "" + cmdLineOptions.testTimeout, "built/local/run.js"]); +task("generate-code-coverage", series(preBuild, buildTests, generateCodeCoverage)); +task("generate-code-coverage").description = "Generates code coverage data via istanbul"; + +const preTest = parallel(buildRules, buildTests, buildServices, buildLssl); +preTest.displayName = "preTest"; + +const postTest = (done) => cmdLineOptions.lint ? lint(done) : done(); + +const runTests = () => runConsoleTests("built/local/run.js", "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ false); +task("runtests", series(preBuild, preTest, runTests, postTest)); +task("runtests").description = "Runs the tests using the built run.js file."; +task("runtests").flags = { + "-t --tests=": "Pattern for tests to run.", + " --failed": "Runs tests listed in '.failed-tests'.", + "-r --reporter=": "The mocha reporter to use.", + "-d --debug": "Runs tests in debug mode (NodeJS 6 and earlier)", + "-i --inspect": "Runs tests in inspector mode (NodeJS 8 and later)", + " --keepFailed": "Keep tests in .failed-tests even if they pass", + " --light": "Run tests in light mode (fewer verifications, but tests run faster)", + " --dirty": "Run tests without first cleaning test output directories", + " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", + " --no-color": "Disables color", + " --no-lint": "Disables lint", + " --timeout=": "Overrides the default test timeout.", + " --built": "Compile using the built version of the compiler.", +} + +const runTestsParallel = () => runConsoleTests("built/local/run.js", "min", /*runInParallel*/ true, /*watchMode*/ false); +task("runtests-parallel", series(preBuild, preTest, runTestsParallel, postTest)); +task("runtests-parallel").description = "Runs all the tests in parallel using the built run.js file."; +task("runtests-parallel").flags = { + " --no-lint": "disables lint.", + " --light": "Run tests in light mode (fewer verifications, but tests run faster).", + " --keepFailed": "Keep tests in .failed-tests even if they pass.", + " --dirty": "Run tests without first cleaning test output directories.", + " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", + " --workers=": "The number of parallel workers to use.", + " --timeout=": "Overrides the default test timeout.", + " --built": "Compile using the built version of the compiler.", +}; + +const buildWebTestServer = () => buildProject("tests/webTestServer.tsconfig.json"); +const cleanWebTestServer = () => cleanProject("tests/webTestServer.tsconfig.json"); +cleanTasks.push(cleanWebTestServer); + +const browserifyTests = () => src(["built/local/run.js"], { base: "built/local" }) + .pipe(newer("built/local/bundle.js")) + .pipe(sourcemaps.init({ loadMaps: true })) + .pipe(browserify()) + .pipe(rename("bundle.js")) + .pipe(sourcemaps.write(".", /**@type {*}*/({ includeContent: false, destPath: "built/local" }))) + .pipe(dest("built/local")); + +const runtestsBrowser = async () => { + await cleanTestDirs(); + const { tests, runners, light } = cmdLineOptions; + const testConfigFile = "test.config"; + await del([testConfigFile]); + if (tests || runners || light) { + writeTestConfigFile(tests, runners, light); + } + const args = ["tests/webTestServer.js"]; + if (cmdLineOptions.browser) { + args.push(cmdLineOptions.browser); + } + if (tests) { + args.push(JSON.stringify(tests)); + } + await exec(process.execPath, args); +}; + +task("runtests-browser", series(preBuild, parallel(buildTests, buildServices, buildLssl, buildWebTestServer), browserifyTests, runtestsBrowser)); +task("runtests-browser").description = "Runs the tests using the built run.js file like 'gulp runtests'."; +task("runtests-browser").flags = { + "-t --tests=": "pattern for tests to run", + "-b --browser=": "Either 'IE' or 'chrome'", + " --built": "Compile using the built version of the compiler.", +}; + +task("diff", () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true })); +task("diff").description = "Diffs the compiler baselines using the diff tool specified by the 'DIFF' environment variable"; + +task("diff-rwc", () => exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], { ignoreExitCode: true })); +task("diff-rwc").description = "Diffs the RWC baselines using the diff tool specified by the 'DIFF' environment variable"; + +const baselineAccept = subfolder => merge2( + src([`${localBaseline}${subfolder ? `${subfolder}/` : ``}**`, `!${localBaseline}${subfolder}/**/*.delete`], { base: localBaseline }) + .pipe(dest(refBaseline)), + src([`${localBaseline}${subfolder ? `${subfolder}/` : ``}**/*.delete`], { base: localBaseline, read: false }) + .pipe(rm()) + .pipe(rename({ extname: "" })) + .pipe(rm(refBaseline))); +task("baseline-accept", () => baselineAccept("")); +task("baseline-accept").description = "Makes the most recent test results the new baseline, overwriting the old baseline"; + +task("baseline-accept-rwc", () => baselineAccept("rwc")); +task("baseline-accept-rwc").description = "Makes the most recent rwc test results the new baseline, overwriting the old baseline"; + +task("baseline-accept-test262", () => baselineAccept("test262")); +task("baseline-accept-test262").description = "Makes the most recent test262 test results the new baseline, overwriting the old baseline"; + +// TODO(rbuckton): Determine if 'webhost' is still in use. +const buildWebHost = () => buildProject("tests/webhost/webtsc.tsconfig.json"); +task("webhost", series(lkgPreBuild, buildWebHost)); +task("webhost").description = "Builds the tsc web host"; + +const cleanWebHost = () => cleanProject("tests/webhost/webtsc.tsconfig.json"); +cleanTasks.push(cleanWebHost); +task("clean-webhost", cleanWebHost); +task("clean-webhost").description = "Cleans the outputs of the tsc web host"; + +// TODO(rbuckton): Determine if 'perftsc' is still in use. +const buildPerfTsc = () => buildProject("tests/perftsc.tsconfig.json"); +task("perftsc", series(lkgPreBuild, buildPerfTsc)); +task("perftsc").description = "Builds augmented version of the compiler for perf tests"; + +const cleanPerfTsc = () => cleanProject("tests/perftsc.tsconfig.json"); +cleanTasks.push(cleanPerfTsc); +task("clean-perftsc", cleanPerfTsc); +task("clean-perftsc").description = "Cleans the outputs of the perftsc project"; + +const buildLoggedIO = async () => { + mkdirp.sync("built/local/temp"); + await exec(process.execPath, ["lib/tsc", "--types", "--target", "es5", "--lib", "es5", "--outdir", "built/local/temp", "src/harness/loggedIO.ts"]); + fs.renameSync("built/local/temp/harness/loggedIO.js", "built/local/loggedIO.js"); + await del("built/local/temp"); +}; + +const cleanLoggedIO = () => del("built/local/temp/loggedIO.js"); +cleanTasks.push(cleanLoggedIO); + +const buildInstrumenter = () => buildProject("src/instrumenter"); +const cleanInstrumenter = () => cleanProject("src/instrumenter"); +cleanTasks.push(cleanInstrumenter); + +const tscInstrumented = () => exec(process.execPath, ["built/local/instrumenter.js", "record", cmdLineOptions.tests || "iocapture", "built/local"]); +task("tsc-instrumented", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildLoggedIO, buildInstrumenter), tscInstrumented)); +task("tsc-instrumented").description = "Builds an instrumented tsc.js"; +task("tsc-instrumented").flags = { + "-t --tests=": "The test to run." +} + +// TODO(rbuckton): Determine if we still need this task. Depending on a relative +// path here seems like a bad idea. +const updateSublime = () => src(["built/local/tsserver.js", "built/local/tsserver.js.map"]) + .pipe(dest("../TypeScript-Sublime-Plugin/tsserver/")); +task("update-sublime", updateSublime); +task("update-sublime").description = "Updates the sublime plugin's tsserver"; + +const buildImportDefinitelyTypedTests = () => buildProject("scripts/importDefinitelyTypedTests"); +const cleanImportDefinitelyTypedTests = () => cleanProject("scripts/importDefinitelyTypedTests"); +cleanTasks.push(cleanImportDefinitelyTypedTests); + +// TODO(rbuckton): Should the path to DefinitelyTyped be configurable via an environment variable? +const importDefinitelyTypedTests = () => exec(process.execPath, ["scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js", "./", "../DefinitelyTyped"]); +task("importDefinitelyTypedTests", series(buildImportDefinitelyTypedTests, importDefinitelyTypedTests)); +task("importDefinitelyTypedTests").description = "Runs the importDefinitelyTypedTests script to copy DT's tests to the TS-internal RWC tests"; + +const buildReleaseTsc = () => buildProject("src/tsc/tsconfig.release.json"); +const cleanReleaseTsc = () => cleanProject("src/tsc/tsconfig.release.json"); +cleanTasks.push(cleanReleaseTsc); + +const buildCancellationToken = () => buildProject("src/cancellationToken"); +const cleanCancellationToken = () => cleanProject("src/cancellationToken"); +cleanTasks.push(cleanCancellationToken); + +const buildTypingsInstaller = () => buildProject("src/typingsInstaller"); +const cleanTypingsInstaller = () => cleanProject("src/typingsInstaller"); +cleanTasks.push(cleanTypingsInstaller); + +const buildWatchGuard = () => buildProject("src/watchGuard"); +const cleanWatchGuard = () => cleanProject("src/watchGuard"); +cleanTasks.push(cleanWatchGuard); + +// TODO(rbuckton): This task isn't triggered by any other task. Is it still needed? +const generateTypesMap = () => src("src/server/typesMap.json") + .pipe(newer("built/local/typesMap.json")) + .pipe(transform(contents => (JSON.parse(contents), contents))) // validates typesMap.json is valid JSON + .pipe(dest("built/local")); +task("generate-types-map", generateTypesMap); + +const cleanTypesMap = () => del("built/local/typesMap.json"); +cleanTasks.push(cleanTypesMap); + +const cleanBuilt = () => del("built"); + +const produceLKG = async () => { const expectedFiles = [ - tscReleaseJs, - typescriptServicesJs, - tsserverJs, - typescriptJs, - typescriptDts, - typescriptServicesDts, - tsserverlibraryDts, - tsserverlibraryDts, - typingsInstallerJs, - cancellationTokenJs - ].concat(libraryTargets); + "built/local/tsc.release.js", + "built/local/typescriptServices.js", + "built/local/typescriptServices.d.ts", + "built/local/tsserver.js", + "built/local/typescript.js", + "built/local/typescript.d.ts", + "built/local/tsserverlibrary.js", + "built/local/tsserverlibrary.d.ts", + "built/local/typingsInstaller.js", + "built/local/cancellationToken.js" + ].concat(libs.map(lib => lib.target)); const missingFiles = expectedFiles .concat(localizationTargets) .filter(f => !fs.existsSync(f)); @@ -301,326 +594,66 @@ gulp.task("produce-LKG", /*help*/ false, ["scripts", "local", cancellationTokenJ throw new Error("Cannot replace the LKG unless all built targets are present in directory 'built/local/'. The following files are missing:\n" + missingFiles.join("\n")); } const sizeBefore = getDirSize("lib"); - return exec(host, [produceLKGJs]).then(() => { - const sizeAfter = getDirSize("lib"); - if (sizeAfter > (sizeBefore * 1.10)) { - throw new Error("The lib folder increased by 10% or more. This likely indicates a bug."); - } - }); + await exec(process.execPath, ["scripts/produceLKG.js"]); + const sizeAfter = getDirSize("lib"); + if (sizeAfter > (sizeBefore * 1.10)) { + throw new Error("The lib folder increased by 10% or more. This likely indicates a bug."); + } +}; + +task("LKG", series(lkgPreBuild, parallel(localize, buildTsc, buildServer, buildServices, buildLssl, buildCancellationToken, buildTypingsInstaller, buildWatchGuard, buildReleaseTsc), produceLKG)); +task("LKG").description = "Makes a new LKG out of the built js files"; +task("LKG").flags = { + " --built": "Compile using the built version of the compiler.", +} + +const generateSpec = () => exec("cscript", ["//nologo", "scripts/word2md.js", path.resolve("doc/TypeScript Language Specification.docx"), path.resolve("doc/spec.md")]); +task("generate-spec", series(buildScripts, generateSpec)); +task("generate-spec").description = "Generates a Markdown version of the Language Specification"; + +task("clean", series(parallel(cleanTasks), cleanBuilt)); +task("clean").description = "Cleans build outputs"; + +const configureNightly = () => exec(process.execPath, ["scripts/configurePrerelease.js", "dev", "package.json", "src/compiler/core.ts"]) +task("configure-nightly", series(buildScripts, configureNightly)); +task("configure-nightly").description = "Runs scripts/configurePrerelease.ts to prepare a build for nightly publishing"; + +const publishNightly = () => exec("npm", ["publish", "--tag", "next"]); +task("publish-nightly", series(task("clean"), task("LKG"), task("clean"), task("runtests-parallel"), publishNightly)); +task("publish-nightly").description = "Runs `npm publish --tag next` to create a new nightly build on npm"; + +// TODO(rbuckton): The problem with watching in this way is that a change in compiler/ will result +// in cascading changes in other projects that may take differing amounts of times to complete. As +// a result, the watch may accidentally trigger early, so we have to set a significant delay. An +// alternative approach would be to leverage a builder API, or to have 'tsc -b' have an option to +// write some kind of trigger file that indicates build completion that we could listen for instead. +const watchRuntests = () => watch(["built/local/*.js", "tests/cases/**/*.ts", "tests/cases/**/tsconfig.json"], { delay: 5000 }, async () => { + if (cmdLineOptions.tests || cmdLineOptions.failed) { + await runConsoleTests("built/local/run.js", "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ true); + } + else { + await runConsoleTests("built/local/run.js", "min", /*runInParallel*/ true, /*watchMode*/ true); + } }); +task("watch", series(preBuild, preTest, parallel(watchLib, watchDiagnostics, watchServices, watchLssl, watchTests, watchRuntests))); +task("watch").description = "Watches for changes and rebuilds and runs tests in parallel."; +task("watch").flags = { + "-t --tests=": "Pattern for tests to run. Forces tests to be run in a single worker.", + " --failed": "Runs tests listed in '.failed-tests'. Forces tests to be run in a single worker.", + "-r --reporter=": "The mocha reporter to use.", + " --keepFailed": "Keep tests in .failed-tests even if they pass", + " --light": "Run tests in light mode (fewer verifications, but tests run faster)", + " --dirty": "Run tests without first cleaning test output directories", + " --stackTraceLimit=": "Sets the maximum number of stack frames to display. Use 'full' to show all frames.", + " --no-color": "Disables color", + " --no-lint": "Disables lint", + " --timeout=": "Overrides the default test timeout.", + " --workers=": "The number of parallel workers to use.", + " --built": "Compile using the built version of the compiler.", +}; -gulp.task( - "LKG", - "Makes a new LKG out of the built js files", - () => runSequence("clean-built", "produce-LKG")); +task("default", series("local")); +task("default").description = "Runs 'local'"; -// Task to build the tests infrastructure using the built compiler -const testRunnerProject = "src/testRunner/tsconfig.json"; -const runJs = "built/local/run.js"; -gulp.task(runJs, /*help*/ false, useCompilerDeps, () => project.compile(testRunnerProject, { typescript: useCompiler })); - -gulp.task( - "tests", - "Builds the test infrastructure using the built compiler", - [runJs, tsserverlibraryDts]); - -gulp.task( - "runtests-parallel", - "Runs all the tests in parallel using the built run.js file. Optional arguments are: --t[ests]=category1|category2|... --d[ebug]=true.", - ["build-rules", "tests", "services", tsserverlibraryDts], - () => runConsoleTests(runJs, "min", /*runInParallel*/ true, /*watchMode*/ false)); - -gulp.task( - "runtests", - "Runs the tests using the built run.js file. Optional arguments are: --t[ests]=regex --r[eporter]=[list|spec|json|] --d[ebug]=true --color[s]=false --lint=true.", - ["build-rules", "tests", "services", tsserverlibraryDts], - () => runConsoleTests(runJs, "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ false)); - -const webTestServerProject = "tests/webTestServer.tsconfig.json"; -const webTestServerJs = "tests/webTestServer.js"; -gulp.task(webTestServerJs, /*help*/ false, useCompilerDeps, () => project.compile(webTestServerProject, { typescript: useCompiler })); -gulp.task(`clean:${webTestServerJs}`, /*help*/ false, () => project.clean(webTestServerProject)); - -const bundlePath = path.resolve("built/local/bundle.js"); - -gulp.task( - "browserify", - "Runs browserify on run.js to produce a file suitable for running tests in the browser", - [runJs], - () => gulp.src([runJs], { base: "built/local" }) - .pipe(newer(bundlePath)) - .pipe(sourcemaps.init({ loadMaps: true })) - .pipe(browserify()) - .pipe(rename("bundle.js")) - .pipe(sourcemaps.write(".", /**@type {*}*/({ includeContent: false, destPath: "built/local" }))) - .pipe(gulp.dest("built/local"))); - -gulp.task( - "runtests-browser", - "Runs the tests using the built run.js file like 'gulp runtests'. Syntax is gulp runtests-browser. Additional optional parameters --tests=[regex], --browser=[chrome|IE]", - ["browserify", webTestServerJs], - () => cleanTestDirs().then(() => { - const tests = cmdLineOptions.tests; - const runners = cmdLineOptions.runners; - const light = cmdLineOptions.light; - const testConfigFile = "test.config"; - if (fs.existsSync(testConfigFile)) { - fs.unlinkSync(testConfigFile); - } - if (tests || runners || light) { - writeTestConfigFile(tests, runners, light); - } - const args = [webTestServerJs]; - if (cmdLineOptions.browser) { - args.push(cmdLineOptions.browser); - } - if (tests) { - args.push(JSON.stringify(tests)); - } - return exec("node", args); - })); - -gulp.task( - "generate-code-coverage", - "Generates code coverage data via istanbul", - ["tests"], - () => exec("istanbul", ["cover", "node_modules/mocha/bin/_mocha", "--", "-R", "min", "-t", "" + cmdLineOptions.testTimeout, runJs])); - - -gulp.task( - "diff", - "Diffs the compiler baselines using the diff tool specified by the 'DIFF' environment variable", - () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true })); - -gulp.task( - "diff-rwc", - "Diffs the RWC baselines using the diff tool specified by the 'DIFF' environment variable", - () => exec(getDiffTool(), [refRwcBaseline, localRwcBaseline], { ignoreExitCode: true })); - -gulp.task( - "baseline-accept", - "Makes the most recent test results the new baseline, overwriting the old baseline", - () => baselineAccept()); - -gulp.task( - "baseline-accept-rwc", - "Makes the most recent rwc test results the new baseline, overwriting the old baseline", - () => baselineAccept("rwc")); - -gulp.task( - "baseline-accept-test262", - "Makes the most recent test262 test results the new baseline, overwriting the old baseline", - () => baselineAccept("test262")); - -// Webhost -const webtscProject = "tests/webhost/webtsc.tsconfig.json"; -const webtscJs = "tests/webhost/webtsc.js"; -gulp.task(webtscJs, /*help*/ false, useCompilerDeps, () => project.compile(webtscProject, { typescript: useCompiler })); -gulp.task(`clean:${webtscJs}`, /*help*/ false, () => project.clean(webtscProject)); - -gulp.task("webhost", "Builds the tsc web host", [webtscJs], () => - gulp.src("built/local/lib.d.ts") - .pipe(gulp.dest("tests/webhost/"))); - -// Perf compiler -const perftscProject = "tests/perftsc.tsconfig.json"; -const perftscJs = "built/local/perftsc.js"; -gulp.task(perftscJs, /*help*/ false, useCompilerDeps, () => project.compile(perftscProject, { typescript: useCompiler })); -gulp.task(`clean:${perftscJs}`, /*help*/ false, () => project.clean(perftscProject)); - -gulp.task( - "perftsc", - "Builds augmented version of the compiler for perf tests", - [perftscJs]); - -// Instrumented compiler -const loggedIOTs = "src/harness/loggedIO.ts"; -const loggedIOJs = "built/local/loggedIO.js"; -gulp.task(loggedIOJs, /*help*/ false, [], (done) => { - return mkdirp("built/local/temp") - .then(() => exec(host, ["lib/tsc.js", "--types", "--target es5", "--lib es5", "--outdir", "built/local/temp", loggedIOTs])) - .then(() => { fs.renameSync(path.join("built/local/temp", "/harness/loggedIO.js"), loggedIOJs); }) - .then(() => del("built/local/temp")); -}); - -const instrumenterProject = "src/instrumenter/tsconfig.json"; -const instrumenterJs = "built/local/instrumenter.js"; -gulp.task(instrumenterJs, /*help*/ false, () => project.compile(instrumenterProject)); -gulp.task(`clean:${instrumenterJs}`, /*help*/ false, () => project.clean(instrumenterProject)); - -gulp.task( - "tsc-instrumented", - "Builds an instrumented tsc.js - run with --test=[testname]", - ["local", loggedIOJs, instrumenterJs, typescriptServicesJs], - () => exec(host, [instrumenterJs, "record", cmdLineOptions.tests || "iocapture", "built/local"])); - -gulp.task( - "update-sublime", - "Updates the sublime plugin's tsserver", - ["local", tsserverJs], - () => - gulp.src([tsserverJs, tsserverJs + ".map"]) - .pipe(gulp.dest("../TypeScript-Sublime-Plugin/tsserver/"))); - -gulp.task( - "build-rules", - "Compiles tslint rules to js", - () => project.compile("scripts/tslint/tsconfig.json")); - -gulp.task("clean-rules", /*help*/ false, () => project.clean("scripts/tslint/tsconfig.json")); - -gulp.task( - "lint", - "Runs tslint on the compiler sources. Optional arguments are: --f[iles]=regex", - ["build-rules"], - () => { - if (fold.isTravis()) console.log(fold.start("lint")); - for (const project of ["scripts/tslint/tsconfig.json", "src/tsconfig-base.json"]) { - const cmd = `node node_modules/tslint/bin/tslint --project ${project} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish${cmdLineOptions.fix ? " --fix" : ""}`; - log("Linting: " + cmd); - child_process.execSync(cmd, { stdio: [0, 1, 2] }); - } - if (fold.isTravis()) console.log(fold.end("lint")); - }); - -gulp.task( - "default", - "Runs 'local'", - ["local"]); - -gulp.task( - "watch-diagnostics", - /*help*/ false, - [processDiagnosticMessagesJs], - () => gulp.watch([diagnosticMessagesJson], [diagnosticInformationMapTs, builtGeneratedDiagnosticMessagesJson])); - -gulp.task( - "watch-lib", - /*help*/ false, - () => gulp.watch(["src/lib/**/*"], ["lib"])); - -gulp.task( - "watch-tsc", - /*help*/ false, - ["watch-diagnostics", "watch-lib"].concat(useCompilerDeps), - () => project.watch(tscProject, { typescript: useCompiler })); - -const watchServicesPatterns = [ - "src/compiler/**/*", - "src/jsTypings/**/*", - "src/services/**/*" -]; - -gulp.task( - "watch-services", - /*help*/ false, - ["watch-diagnostics", "watch-lib"], - () => gulp.watch(watchServicesPatterns, ["services"])); - -const watchLsslPatterns = [ - ...watchServicesPatterns, - "src/server/**/*", - "src/tsserver/tsconfig.json" -]; - -gulp.task( - "watch-lssl", - /*help*/ false, - () => gulp.watch(watchLsslPatterns, ["lssl"])); - -gulp.task( - "watch-server", - /*help*/ false, - ["watch-diagnostics", "watch-lib"].concat(useCompilerDeps), - () => project.watch(tsserverProject, { typescript: useCompiler })); - -gulp.task( - "watch-runner", - /*help*/ false, - useCompilerDeps, - () => project.watch(testRunnerProject, { typescript: useCompiler })); - -gulp.task( - "watch-local", - "Watches for changes to projects in src/ (but does not execute tests).", - ["watch-lib", "watch-tsc", "watch-services", "watch-server", "watch-runner", "watch-lssl"]); - -gulp.task( - "watch", - "Watches for changes to the build inputs for built/local/run.js, then runs tests.", - ["build-rules", "watch-runner", "watch-services", "watch-lssl"], - () => { - const sem = new Semaphore(1); - - gulp.watch([runJs, typescriptDts, tsserverlibraryDts], () => { - runTests(); - }); - - // NOTE: gulp.watch is far too slow when watching tests/cases/**/* as it first enumerates *every* file - const testFilePattern = /(\.ts|[\\/]tsconfig\.json)$/; - fs.watch("tests/cases", { recursive: true }, (_, file) => { - if (testFilePattern.test(file)) runTests(); - }); - - async function runTests() { - try { - // Ensure only one instance of the test runner is running at any given time. - if (sem.count > 0) { - await sem.wait(); - try { - // Wait for any concurrent recompilations to complete... - try { - await delay(100); - while (project.hasRemainingWork()) { - await project.waitForWorkToComplete(); - await delay(500); - } - } - catch (e) { - if (e instanceof CancelError) return; - throw e; - } - - // cancel any pending or active test run if a new recompilation is triggered - const source = new CancellationTokenSource(); - project.waitForWorkToStart().then(() => { - source.cancel(); - }); - - if (cmdLineOptions.tests || cmdLineOptions.failed) { - await runConsoleTests(runJs, "mocha-fivemat-progress-reporter", /*runInParallel*/ false, /*watchMode*/ true, source.token); - } - else { - await runConsoleTests(runJs, "min", /*runInParallel*/ true, /*watchMode*/ true, source.token); - } - } - finally { - sem.release(); - } - } - } - catch (e) { - if (e instanceof CancelError) { - log.warn("Operation was canceled"); - } - else { - log.error(e); - } - } - }; - }); - -gulp.task("clean-built", /*help*/ false, [`clean:${diagnosticInformationMapTs}`], () => del(["built"])); -gulp.task( - "clean", - "Cleans the compiler output, declare files, and tests", - [ - `clean:${importDefinitelyTypedTestsJs}`, - `clean:${webtscJs}`, - `clean:${perftscJs}`, - `clean:${instrumenterJs}`, - `clean:${webTestServerJs}`, - "clean:scripts", - "clean-rules", - "clean-built" - ]); \ No newline at end of file +task("help", () => exec("gulp", ["--tasks", "--depth", "1", "--sort-tasks"], { hidePrompt: true })); +task("help").description = "Prints the top-level tasks."; diff --git a/Jakefile.js b/Jakefile.js index 1413230c079..ea81881e7d9 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -8,10 +8,8 @@ const path = require("path"); const fold = require("travis-fold"); const ts = require("./lib/typescript"); const del = require("del"); -const getDirSize = require("./scripts/build/getDirSize"); +const { getDirSize, needsUpdate, flatten } = require("./scripts/build/utils"); const { base64VLQFormatEncode } = require("./scripts/build/sourcemaps"); -const needsUpdate = require("./scripts/build/needsUpdate"); -const { flatten } = require("./scripts/build/project"); // add node_modules to path so we don't need global modules, prefer the modules by adding them first var nodeModulesPathPrefix = path.resolve("./node_modules/.bin/") + path.delimiter; @@ -361,7 +359,7 @@ file(ConfigFileFor.tsserverLibrary, [], function () { compilerOptions: { "removeComments": false, "stripInternal": true, - "declarationMap": false, + "declaration": true, "outFile": "tsserverlibrary.out.js" } }) diff --git a/README.md b/README.md index a7222668ada..2826db8aec6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Build Status](https://travis-ci.org/Microsoft/TypeScript.svg?branch=master)](https://travis-ci.org/Microsoft/TypeScript) -[![VSTS Build Status](https://typescript.visualstudio.com/_apis/public/build/definitions/cf7ac146-d525-443c-b23c-0d58337efebc/4/badge)](https://typescript.visualstudio.com/TypeScript/_build/latest?definitionId=4&view=logs) +[![VSTS Build Status](https://dev.azure.com/typescript/TypeScript/_apis/build/status/Typescript/node10)](https://dev.azure.com/typescript/TypeScript/_build/latest?definitionId=4&view=logs) [![npm version](https://badge.fury.io/js/typescript.svg)](https://www.npmjs.com/package/typescript) [![Downloads](https://img.shields.io/npm/dm/typescript.svg)](https://www.npmjs.com/package/typescript) diff --git a/doc/spec.md b/doc/spec.md index c6dd8cb4d06..741d4f29cec 100644 --- a/doc/spec.md +++ b/doc/spec.md @@ -239,7 +239,7 @@ TypeScript is a trademark of Microsoft Corporation. # 1 Introduction -JavaScript applications such as web e-mail, maps, document editing, and collaboration tools are becoming an increasingly important part of the everyday computing. We designed TypeScript to meet the needs of the JavaScript programming teams that build and maintain large JavaScript programs. TypeScript helps programming teams to define interfaces between software components and to gain insight into the behavior of existing JavaScript libraries. TypeScript also enables teams to reduce naming conflicts by organizing their code into dynamically-loadable modules. TypeScript's optional type system enables JavaScript programmers to use highly-productive development tools and practices: static checking, symbol-based navigation, statement completion, and code re-factoring. +JavaScript applications such as web e-mail, maps, document editing, and collaboration tools are becoming an increasingly important part of the everyday computing. We designed TypeScript to meet the needs of the JavaScript programming teams that build and maintain large JavaScript programs. TypeScript helps programming teams to define interfaces between software components and to gain insight into the behavior of existing JavaScript libraries. TypeScript also enables teams to reduce naming conflicts by organizing their code into dynamically-loadable modules. TypeScript's optional type system enables JavaScript programmers to use highly-productive development tools and practices: static checking, symbol-based navigation, statement completion, and code refactoring. TypeScript is a syntactic sugar for JavaScript. TypeScript syntax is a superset of ECMAScript 2015 (ES2015) syntax. Every JavaScript program is also a TypeScript program. The TypeScript compiler performs only file-local transformations on TypeScript programs and does not re-order variables declared in TypeScript. This leads to JavaScript output that closely matches the TypeScript input. TypeScript does not transform variable names, making tractable the direct debugging of emitted JavaScript. TypeScript optionally provides source maps, enabling source-level debugging. TypeScript tools typically emit JavaScript upon file save, preserving the test, edit, refresh cycle commonly used in JavaScript development. @@ -263,7 +263,7 @@ function f() { } ``` -To benefit from this inference, a programmer can use the TypeScript language service. For example, a code editor can incorporate the TypeScript language service and use the service to find the members of a string object as in the following screen shot. +To benefit from this inference, a programmer can use the TypeScript language service. For example, a code editor can incorporate the TypeScript language service and use the service to find the members of a string object as in the following screenshot.   ![](images/image1.png) @@ -411,7 +411,7 @@ We mentioned above that the '$' function behaves differently depending on the ty This signature denotes that a function may be passed as the parameter of the '$' function. When a function is passed to '$', the jQuery library will invoke that function when a DOM document is ready. Because TypeScript supports overloading, tools can use TypeScript to show all available function signatures with their documentation tips and to give the correct documentation once a function has been called with a particular signature. -A typical client would not need to add any additional typing but could just use a community-supplied typing to discover (through statement completion with documentation tips) and verify (through static checking) correct use of the library, as in the following screen shot. +A typical client would not need to add any additional typing but could just use a community-supplied typing to discover (through statement completion with documentation tips) and verify (through static checking) correct use of the library, as in the following screenshot.   ![](images/image2.png) @@ -628,7 +628,7 @@ JavaScript implementations can use these explicit constants to generate efficien An important goal of TypeScript is to provide accurate and straightforward types for existing JavaScript programming patterns. To that end, TypeScript includes generic types, discussed in the next section, and *overloading on string parameters*, the topic of this section. -JavaScript programming interfaces often include functions whose behavior is discriminated by a string constant passed to the function. The Document Object Model makes heavy use of this pattern. For example, the following screen shot shows that the 'createElement' method of the 'document' object has multiple signatures, some of which identify the types returned when specific strings are passed into the method. +JavaScript programming interfaces often include functions whose behavior is discriminated by a string constant passed to the function. The Document Object Model makes heavy use of this pattern. For example, the following screenshot shows that the 'createElement' method of the 'document' object has multiple signatures, some of which identify the types returned when specific strings are passed into the method.   ![](images/image3.png) @@ -639,7 +639,7 @@ var span = document.createElement("span"); span.isMultiLine = false; // OK: HTMLSpanElement has isMultiline property ``` -In the following screen shot, a programming tool combines information from overloading on string parameters with contextual typing to infer that the type of the variable 'e' is 'MouseEvent' and that therefore 'e' has a 'clientX' property. +In the following screenshot, a programming tool combines information from overloading on string parameters with contextual typing to infer that the type of the variable 'e' is 'MouseEvent' and that therefore 'e' has a 'clientX' property.   ![](images/image4.png) @@ -3715,7 +3715,7 @@ the array literal initializer expression is contextually typed by the implied ty ## 5.3 Let and Const Declarations -Let and const declarations are exended to include optional type annotations. +Let and const declarations are extended to include optional type annotations.   *LexicalBinding:* *( Modified )*    *SimpleLexicalBinding* diff --git a/lib/lib.es5.d.ts b/lib/lib.es5.d.ts index d1a9d6f0d2a..068ef13470a 100644 --- a/lib/lib.es5.d.ts +++ b/lib/lib.es5.d.ts @@ -1046,14 +1046,14 @@ interface JSON { * @param reviver A function that transforms the results. This function is called for each member of the object. * If a member contains nested objects, the nested objects are transformed before the parent object is. */ - parse(text: string, reviver?: (key: any, value: any) => any): any; + parse(text: string, reviver?: (this: any, key: string, value: any) => any): any; /** * Converts a JavaScript value to a JavaScript Object Notation (JSON) string. * @param value A JavaScript value, usually an object or array, to be converted. * @param replacer A function that transforms the results. * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. */ - stringify(value: any, replacer?: (key: string, value: any) => any, space?: string | number): string; + stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; /** * Converts a JavaScript value to a JavaScript Object Notation (JSON) string. * @param value A JavaScript value, usually an object or array, to be converted. diff --git a/package.json b/package.json index d8c514363e5..4f4f7db571d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "typescript", "author": "Microsoft Corp.", "homepage": "https://www.typescriptlang.org/", - "version": "3.3.0", + "version": "3.4.0", "license": "Apache-2.0", "description": "TypeScript is a language for application scale JavaScript development", "keywords": [ @@ -35,10 +35,8 @@ "@types/convert-source-map": "latest", "@types/del": "latest", "@types/glob": "latest", - "@types/gulp": "3.X", + "@types/gulp": "^4.0.5", "@types/gulp-concat": "latest", - "@types/gulp-help": "latest", - "@types/gulp-if": "0.0.33", "@types/gulp-newer": "latest", "@types/gulp-rename": "0.0.33", "@types/gulp-sourcemaps": "0.0.32", @@ -50,7 +48,6 @@ "@types/mocha": "latest", "@types/node": "8.5.5", "@types/q": "latest", - "@types/run-sequence": "latest", "@types/source-map-support": "latest", "@types/through2": "latest", "@types/travis-fold": "latest", @@ -63,16 +60,12 @@ "del": "latest", "fancy-log": "latest", "fs-extra": "^6.0.1", - "gulp": "3.X", - "gulp-clone": "latest", + "gulp": "^4.0.0", "gulp-concat": "latest", - "gulp-help": "latest", - "gulp-if": "latest", "gulp-insert": "latest", "gulp-newer": "latest", "gulp-rename": "latest", "gulp-sourcemaps": "latest", - "gulp-typescript": "latest", "istanbul": "latest", "jake": "latest", "lodash": "4.17.10", @@ -86,7 +79,6 @@ "prex": "^0.4.3", "q": "latest", "remove-internal": "^2.9.2", - "run-sequence": "latest", "source-map-support": "latest", "through2": "latest", "travis-fold": "latest", diff --git a/scripts/authors.ts b/scripts/authors.ts index b616ceb8601..b52898244b6 100644 --- a/scripts/authors.ts +++ b/scripts/authors.ts @@ -4,7 +4,7 @@ import child_process = require("child_process"); type Author = { displayNames: string[]; - preferedName?: string; + preferredName?: string; emails: string[]; }; @@ -20,7 +20,7 @@ const authorsPath = path.resolve("../AUTHORS.md"); function getKnownAuthors(): Author[] { const segmentRegExp = /\s?([^<]+)\s+<([^>]+)>/g; - const preferedNameRegeExp = /\s?#\s?([^#]+)$/; + const preferredNameRegeExp = /\s?#\s?([^#]+)$/; const knownAuthors: Author[] = []; if (!fs.existsSync(mailMapPath)) { @@ -37,13 +37,13 @@ function getKnownAuthors(): Author[] { author.displayNames.push(match[1]); author.emails.push(match[2]); } - if (match = preferedNameRegeExp.exec(line)) { - author.preferedName = match[1]; + if (match = preferredNameRegeExp.exec(line)) { + author.preferredName = match[1]; } if (!author.emails) continue; knownAuthors.push(author); - if (line.indexOf("#") > 0 && !author.preferedName) { - throw new Error("Could not match prefered name for: " + line); + if (line.indexOf("#") > 0 && !author.preferredName) { + throw new Error("Could not match preferred name for: " + line); } // console.log("===> line: " + line); // console.log(JSON.stringify(author, undefined, 2)); @@ -52,7 +52,7 @@ function getKnownAuthors(): Author[] { } function getAuthorName(author: Author) { - return author.preferedName || author.displayNames[0]; + return author.preferredName || author.displayNames[0]; } function getKnownAuthorMaps() { diff --git a/scripts/bisect-test.ts b/scripts/bisect-test.ts index 948b272470f..cc0248f6d64 100644 --- a/scripts/bisect-test.ts +++ b/scripts/bisect-test.ts @@ -5,7 +5,7 @@ import cp = require('child_process'); import fs = require('fs'); -// Slice off 'node bisect-test.js' from the commandline args +// Slice off 'node bisect-test.js' from the command line args var args = process.argv.slice(2); function tsc(tscArgs: string, onExit: (exitCode: number) => void) { diff --git a/scripts/build/baselineAccept.js b/scripts/build/baselineAccept.js deleted file mode 100644 index df61b87cd32..00000000000 --- a/scripts/build/baselineAccept.js +++ /dev/null @@ -1,24 +0,0 @@ -// @ts-check -const merge2 = require("merge2"); -const gulp = require("./gulp"); -const rename = require("gulp-rename"); -const rm = require("./rm"); -const { localBaseline, refBaseline } = require("./tests"); - -module.exports = baselineAccept; - -function baselineAccept(subfolder = "") { - return merge2(baselineCopy(subfolder), baselineDelete(subfolder)); -} - -function baselineCopy(subfolder = "") { - return gulp.src([`${localBaseline}${subfolder ? `${subfolder}/` : ``}**`, `!${localBaseline}${subfolder}/**/*.delete`], { base: localBaseline }) - .pipe(gulp.dest(refBaseline)); -} - -function baselineDelete(subfolder = "") { - return gulp.src([`${localBaseline}${subfolder ? `${subfolder}/` : ``}**/*.delete`], { base: localBaseline, read: false }) - .pipe(rm()) - .pipe(rename({ extname: "" })) - .pipe(rm(refBaseline)); -} diff --git a/scripts/build/browserify.js b/scripts/build/browserify.js index 1fa5bbdeaf3..ba8ccd15824 100644 --- a/scripts/build/browserify.js +++ b/scripts/build/browserify.js @@ -1,12 +1,10 @@ // @ts-check const browserify = require("browserify"); -const Vinyl = require("./vinyl"); +const Vinyl = require("vinyl"); const { Transform } = require("stream"); const { streamFromFile } = require("./utils"); const { replaceContents } = require("./sourcemaps"); -module.exports = browserifyFile; - /** * @param {import("browserify").Options} [opts] */ @@ -31,4 +29,5 @@ function browserifyFile(opts) { } } }); -} \ No newline at end of file +} +exports.browserify = browserifyFile; \ No newline at end of file diff --git a/scripts/build/chalk.js b/scripts/build/chalk.js deleted file mode 100644 index 149c8ea1533..00000000000 --- a/scripts/build/chalk.js +++ /dev/null @@ -1,5 +0,0 @@ -// @ts-check - -// this just fixes the incorrect types for chalk :/ -const chalk = /**@type {import("chalk").Chalk}*/(require("chalk").default || require("chalk")); -module.exports = chalk; \ No newline at end of file diff --git a/scripts/build/convertConstEnum.js b/scripts/build/convertConstEnum.js deleted file mode 100644 index 00d48fdcc3f..00000000000 --- a/scripts/build/convertConstEnum.js +++ /dev/null @@ -1,19 +0,0 @@ -// @ts-check -const replace = require("./replace"); - -module.exports = exports = convertConstEnum; - -/** - * This regexp exists to capture our const enums and replace them with normal enums in our public API - * - this is fine since we compile with preserveConstEnums, and ensures our consumers are not locked - * to the TS version they compile with. - */ -const constEnumCaptureRegexp = /^(\s*)(export )?const enum (\S+) {(\s*)$/gm; -const constEnumReplacement = "$1$2enum $3 {$4"; - -/** - * Converts `const enum` declarations in a .d.ts file into non-const `enum` declarations. - */ -function convertConstEnum() { - return replace(constEnumCaptureRegexp, constEnumReplacement); -} \ No newline at end of file diff --git a/scripts/build/debounce.js b/scripts/build/debounce.js deleted file mode 100644 index 7020cb61bbd..00000000000 --- a/scripts/build/debounce.js +++ /dev/null @@ -1,31 +0,0 @@ -// @ts-check -module.exports = debounce; - -/** - * @param {() => void} cb - * @param {number} timeout - * @param {DebounceOptions} [opts] - * - * @typedef DebounceOptions - * @property {number} [max] - */ -function debounce(cb, timeout, opts = {}) { - if (timeout < 10) timeout = 10; - let max = opts.max || 10; - if (max < timeout) max = timeout; - let minTimer; - let maxTimer; - return trigger; - - function trigger() { - if (max > timeout && !maxTimer) maxTimer = setTimeout(done, max); - if (minTimer) clearTimeout(minTimer); - minTimer = setTimeout(done, timeout); - } - - function done() { - if (maxTimer) maxTimer = void clearTimeout(maxTimer); - if (minTimer) minTimer = void clearTimeout(minTimer); - cb(); - } -} \ No newline at end of file diff --git a/scripts/build/diagnostics.js b/scripts/build/diagnostics.js deleted file mode 100644 index 5ca51c572ea..00000000000 --- a/scripts/build/diagnostics.js +++ /dev/null @@ -1,49 +0,0 @@ -// @ts-check -const ts = require("../../lib/typescript"); -const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util) - -/** @type {FormatDiagnosticsHost} */ -const formatDiagnosticsHost = exports.formatDiagnosticsHost = { - getCanonicalFileName: fileName => fileName, - getCurrentDirectory: () => process.cwd(), - getNewLine: () => ts.sys.newLine -}; - -/** - * @param {Diagnostic[]} diagnostics - * @param {{ cwd?: string, pretty?: boolean }} [options] - */ -function formatDiagnostics(diagnostics, options) { - return options && options.pretty - ? ts.formatDiagnosticsWithColorAndContext(diagnostics, getFormatDiagnosticsHost(options && options.cwd)) - : ts.formatDiagnostics(diagnostics, getFormatDiagnosticsHost(options && options.cwd)); -} -exports.formatDiagnostics = formatDiagnostics; - -/** - * @param {Diagnostic[]} diagnostics - * @param {{ cwd?: string }} [options] - */ -function reportDiagnostics(diagnostics, options) { - log(formatDiagnostics(diagnostics, { cwd: options && options.cwd, pretty: process.stdout.isTTY })); -} -exports.reportDiagnostics = reportDiagnostics; - -/** - * @param {string | undefined} cwd - * @returns {FormatDiagnosticsHost} - */ -function getFormatDiagnosticsHost(cwd) { - if (!cwd || cwd === process.cwd()) return formatDiagnosticsHost; - return { - getCanonicalFileName: formatDiagnosticsHost.getCanonicalFileName, - getCurrentDirectory: () => cwd, - getNewLine: formatDiagnosticsHost.getNewLine - }; -} - -/** - * @typedef {import("../../lib/typescript").FormatDiagnosticsHost} FormatDiagnosticsHost - * @typedef {import("../../lib/typescript").Diagnostic} Diagnostic - */ -void 0; \ No newline at end of file diff --git a/scripts/build/exec.js b/scripts/build/exec.js deleted file mode 100644 index 8e0a058fed0..00000000000 --- a/scripts/build/exec.js +++ /dev/null @@ -1,58 +0,0 @@ -// @ts-check -const cp = require("child_process"); -const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util) -const isWin = /^win/.test(process.platform); -const chalk = require("./chalk"); -const { CancellationToken, CancelError } = require("prex"); - -module.exports = exec; - -/** - * Executes the provided command once with the supplied arguments. - * @param {string} cmd - * @param {string[]} args - * @param {ExecOptions} [options] - * - * @typedef ExecOptions - * @property {boolean} [ignoreExitCode] - * @property {import("prex").CancellationToken} [cancelToken] - */ -function exec(cmd, args, options = {}) { - return /**@type {Promise<{exitCode: number}>}*/(new Promise((resolve, reject) => { - const { ignoreExitCode, cancelToken = CancellationToken.none } = options; - cancelToken.throwIfCancellationRequested(); - - // TODO (weswig): Update child_process types to add windowsVerbatimArguments to the type definition - const subshellFlag = isWin ? "/c" : "-c"; - const command = isWin ? [possiblyQuote(cmd), ...args] : [`${cmd} ${args.join(" ")}`]; - - log(`> ${chalk.green(cmd)} ${args.join(" ")}`); - const proc = cp.spawn(isWin ? "cmd" : "/bin/sh", [subshellFlag, ...command], { stdio: "inherit", windowsVerbatimArguments: true }); - const registration = cancelToken.register(() => { - log(`${chalk.red("killing")} '${chalk.green(cmd)} ${args.join(" ")}'...`); - proc.kill("SIGINT"); - proc.kill("SIGTERM"); - reject(new CancelError()); - }); - proc.on("exit", exitCode => { - registration.unregister(); - if (exitCode === 0 || ignoreExitCode) { - resolve({ exitCode }); - } - else { - reject(new Error(`Process exited with code: ${exitCode}`)); - } - }); - proc.on("error", error => { - registration.unregister(); - reject(error); - }); - })); -} - -/** - * @param {string} cmd - */ -function possiblyQuote(cmd) { - return cmd.indexOf(" ") >= 0 ? `"${cmd}"` : cmd; -} diff --git a/scripts/build/finished.js b/scripts/build/finished.js deleted file mode 100644 index e642c064193..00000000000 --- a/scripts/build/finished.js +++ /dev/null @@ -1,46 +0,0 @@ -// @ts-check -module.exports = finished; - -/** - * @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream - * @returns {Promise} - */ -function finished(stream) { - return new Promise((resolve, reject) => { - const readable = "readable" in stream && stream.readable; - const writable = "writable" in stream && stream.writable; - - let countdown = 0; - const cleanup = () => { - if (readable) stream.removeListener("end", signal); - if (writable) stream.removeListener("finish", signal); - stream.removeListener("error", onerror); - }; - const signal = () => { - if (countdown > 0) { - countdown--; - if (countdown === 0) { - cleanup(); - resolve(); - } - } - }; - const onerror = (error) => { - if (countdown > 0) { - countdown = 0; - cleanup(); - reject(error); - } - }; - stream.once("error", onerror); - if (readable) { - countdown++; - stream.once("end", signal); - } - if (writable) { - countdown++; - stream.once("finish", signal); - } - if (countdown === 0) signal(); - }); -} \ No newline at end of file diff --git a/scripts/build/getDiffTool.js b/scripts/build/getDiffTool.js deleted file mode 100644 index 13a30b89337..00000000000 --- a/scripts/build/getDiffTool.js +++ /dev/null @@ -1,12 +0,0 @@ -// @ts-check -const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util) -module.exports = getDiffTool; - -function getDiffTool() { - const program = process.env.DIFF; - if (!program) { - log.warn("Add the 'DIFF' environment variable to the path of the program you want to use."); - process.exit(1); - } - return program; -} \ No newline at end of file diff --git a/scripts/build/getDirSize.js b/scripts/build/getDirSize.js deleted file mode 100644 index 58a65907e4d..00000000000 --- a/scripts/build/getDirSize.js +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-check -const { lstatSync, readdirSync } = require("fs"); -const { join } = require("path"); - -/** - * Find the size of a directory recursively. - * Symbolic links can cause a loop. - * @param {string} root - * @returns {number} bytes - */ -function getDirSize(root) { - const stats = lstatSync(root); - - if (!stats.isDirectory()) { - return stats.size; - } - - return readdirSync(root) - .map(file => getDirSize(join(root, file))) - .reduce((acc, num) => acc + num, 0); -} - -module.exports = getDirSize; diff --git a/scripts/build/gulp-typescript-oop/index.js b/scripts/build/gulp-typescript-oop/index.js deleted file mode 100644 index ed7a7d64a03..00000000000 --- a/scripts/build/gulp-typescript-oop/index.js +++ /dev/null @@ -1,149 +0,0 @@ -// @ts-check -const path = require("path"); -const child_process = require("child_process"); -const fs = require("fs"); -const tsc = require("gulp-typescript"); -const Vinyl = require("vinyl"); -const { Duplex, Readable } = require("stream"); -const protocol = require("./protocol"); - -/** - * @param {string | undefined} tsConfigFileName - * @param {tsc.Settings} settings - * @param {CreateProjectOptions} options - * - * @typedef CreateProjectOptions - * @property {string} [typescript] - * @property {boolean} [parse] - */ -function createProject(tsConfigFileName, settings, options) { - settings = Object.assign({}, settings); - options = Object.assign({}, options); - if (settings.typescript) throw new Error(); - - const localSettings = Object.assign({}, settings); - if (options.typescript) { - options.typescript = path.resolve(options.typescript); - localSettings.typescript = require(options.typescript); - } - - const project = tsConfigFileName === undefined ? tsc.createProject(localSettings) : tsc.createProject(tsConfigFileName, localSettings); - const wrappedProject = /** @type {tsc.Project} */((reporter = tsc.reporter.defaultReporter()) => { - const ts = project.typescript; - const proc = child_process.fork(require.resolve("./worker.js"), [], { - // Prevent errors when debugging gulpfile due to the same debug port being passed to forked children. - execArgv: [] - }); - /** @type {Map} */ - const inputs = new Map(); - /** @type {Map} */ - const sourceFiles = new Map(); - /** @type {protocol.SourceFileHost & protocol.VinylHost} */ - const host = { - getVinyl(path) { return inputs.get(path); }, - getSourceFile(fileName) { return sourceFiles.get(fileName); }, - createSourceFile(fileName, text, languageVersion) { - if (text === undefined) { - text = fs.readFileSync(fileName, "utf8"); - } - - /** @type {protocol.SourceFile} */ - let file; - if (options.parse) { - file = ts.createSourceFile(fileName, text, languageVersion, /*setParentNodes*/ true); - } - else { - // NOTE: the built-in reporters in gulp-typescript don't actually need a full - // source file, so save time by faking one unless requested. - file = /**@type {protocol.SourceFile}*/({ - pos: 0, - end: text.length, - kind: ts.SyntaxKind.SourceFile, - fileName, - text, - languageVersion, - statements: /**@type {*} */([]), - endOfFileToken: { pos: text.length, end: text.length, kind: ts.SyntaxKind.EndOfFileToken }, - amdDependencies: /**@type {*} */([]), - referencedFiles: /**@type {*} */([]), - typeReferenceDirectives: /**@type {*} */([]), - libReferenceDirectives: /**@type {*} */([]), - languageVariant: ts.LanguageVariant.Standard, - isDeclarationFile: /\.d\.ts$/.test(fileName), - hasNoDefaultLib: /[\\/]lib\.[^\\/]+\.d\.ts$/.test(fileName) - }); - } - sourceFiles.set(fileName, file); - return file; - } - }; - /** @type {Duplex & { js?: Readable, dts?: Readable }} */ - const compileStream = new Duplex({ - objectMode: true, - read() {}, - /** @param {*} file */ - write(file, _encoding, callback) { - inputs.set(file.path, file); - proc.send(protocol.message.write(file)); - callback(); - }, - final(callback) { - proc.send(protocol.message.final()); - callback(); - } - }); - const jsStream = compileStream.js = new Readable({ - objectMode: true, - read() {} - }); - const dtsStream = compileStream.dts = new Readable({ - objectMode: true, - read() {} - }); - proc.send(protocol.message.createProject(tsConfigFileName, settings, options)); - proc.on("message", (/**@type {protocol.WorkerMessage}*/ message) => { - switch (message.method) { - case "write": { - const file = protocol.vinylFromJson(message.params); - compileStream.push(file); - if (file.path.endsWith(".d.ts")) { - dtsStream.push(file); - } - else { - jsStream.push(file); - } - break; - } - case "final": { - compileStream.push(null); - jsStream.push(null); - dtsStream.push(null); - proc.kill(); // TODO(rbuckton): pool workers? may not be feasible due to gulp-typescript holding onto memory - break; - } - case "error": { - const error = protocol.errorFromJson(message.params); - compileStream.emit("error", error); - proc.kill(); // TODO(rbuckton): pool workers? may not be feasible due to gulp-typescript holding onto memory - break; - } - case "reporter.error": { - if (reporter.error) { - const error = protocol.typeScriptErrorFromJson(message.params, host); - reporter.error(error, project.typescript); - } - break; - } - case "reporter.finish": { - if (reporter.finish) { - reporter.finish(message.params); - } - } - } - }); - return /** @type {*} */(compileStream); - }); - return Object.assign(wrappedProject, project); -} - -exports.createProject = createProject; \ No newline at end of file diff --git a/scripts/build/gulp-typescript-oop/protocol.js b/scripts/build/gulp-typescript-oop/protocol.js deleted file mode 100644 index 714b94f22a1..00000000000 --- a/scripts/build/gulp-typescript-oop/protocol.js +++ /dev/null @@ -1,281 +0,0 @@ -// @ts-check -const Vinyl = require("vinyl"); - -/** - * @param {File} file - * @returns {*} - */ -function vinylToJson(file) { - if (file.isStream()) throw new TypeError("Streams not supported."); - return { - path: file.path, - cwd: file.cwd, - base: file.base, - contents: file.isBuffer() ? file.contents.toString("utf8") : undefined, - sourceMap: file.sourceMap - }; -} -exports.vinylToJson = vinylToJson; - -/** - * @param {*} json - * @returns {File} - */ -function vinylFromJson(json) { - return new Vinyl({ - path: json.path, - cwd: json.cwd, - base: json.base, - contents: typeof json.contents === "string" ? Buffer.from(json.contents, "utf8") : undefined, - sourceMap: json.sourceMap - }); -} -exports.vinylFromJson = vinylFromJson; - -/** - * @param {Error} error - * @returns {*} - */ -function errorToJson(error) { - return { - name: error.name, - message: error.message, - stack: error.stack - }; -} -exports.errorToJson = errorToJson; - -/** - * @param {*} json - * @returns {Error} - */ -function errorFromJson(json) { - const error = new Error(); - error.name = json.name; - error.message = json.message; - error.stack = json.stack; - return error; -} -exports.errorFromJson = errorFromJson; - -/** - * @param {TypeScriptError} error - * @returns {*} - */ -function typeScriptErrorToJson(error) { - return Object.assign({}, errorToJson(error), { - fullFilename: error.fullFilename, - relativeFilename: error.relativeFilename, - file: error.file && { path: error.file.path }, - tsFile: error.tsFile && sourceFileToJson(error.tsFile), - diagnostic: diagnosticToJson(error.diagnostic), - startPosition: error.startPosition, - endPosition: error.endPosition - }); -} -exports.typeScriptErrorToJson = typeScriptErrorToJson; - -/** - * @param {*} json - * @param {SourceFileHost & VinylHost} host - * @returns {TypeScriptError} - */ -function typeScriptErrorFromJson(json, host) { - const error = /**@type {TypeScriptError}*/(errorFromJson(json)); - error.fullFilename = json.fullFilename; - error.relativeFilename = json.relativeFilename; - error.file = json.file && host.getVinyl(json.file.path); - error.tsFile = json.tsFile && sourceFileFromJson(json.tsFile, host); - error.diagnostic = diagnosticFromJson(json.diagnostic, host); - error.startPosition = json.startPosition; - error.endPosition = json.endPosition; - return error; -} -exports.typeScriptErrorFromJson = typeScriptErrorFromJson; - -/** - * @param {SourceFile} file - * @returns {*} - */ -function sourceFileToJson(file) { - return { - fileName: file.fileName, - text: file.text, - languageVersion: file.languageVersion - }; -} -exports.sourceFileToJson = sourceFileToJson; - -/** - * @param {*} json - * @param {SourceFileHost} host - */ -function sourceFileFromJson(json, host) { - return host.getSourceFile(json.fileName) - || host.createSourceFile(json.fileName, json.text, json.languageVersion); -} -exports.sourceFileFromJson = sourceFileFromJson; - -/** - * @param {Diagnostic} diagnostic - * @returns {*} - */ -function diagnosticToJson(diagnostic) { - return Object.assign({}, diagnosticRelatedInformationToJson(diagnostic), { - category: diagnostic.category, - code: diagnostic.code, - source: diagnostic.source, - relatedInformation: diagnostic.relatedInformation && diagnostic.relatedInformation.map(diagnosticRelatedInformationToJson) - }); -} -exports.diagnosticToJson = diagnosticToJson; - -/** - * @param {*} json - * @param {SourceFileHost} host - * @returns {Diagnostic} - */ -function diagnosticFromJson(json, host) { - return Object.assign({}, diagnosticRelatedInformationFromJson(json, host), { - category: json.category, - code: json.code, - source: json.source, - relatedInformation: json.relatedInformation && json.relatedInformation.map(json => diagnosticRelatedInformationFromJson(json, host)) - }); -} -exports.diagnosticFromJson = diagnosticFromJson; - -/** - * @param {DiagnosticRelatedInformation} diagnostic - * @returns {*} - */ -function diagnosticRelatedInformationToJson(diagnostic) { - return { - file: diagnostic.file && { fileName: diagnostic.file.fileName }, - start: diagnostic.start, - length: diagnostic.length, - messageText: diagnostic.messageText - }; -} -exports.diagnosticRelatedInformationToJson = diagnosticRelatedInformationToJson; - -/** - * @param {*} json - * @param {SourceFileHost} host - * @returns {DiagnosticRelatedInformation} - */ -function diagnosticRelatedInformationFromJson(json, host) { - return { - file: json.file && sourceFileFromJson(json.file, host), - start: json.start, - length: json.length, - messageText: json.messageText, - category: json.category, - code: json.code - }; -} -exports.diagnosticRelatedInformationFromJson = diagnosticRelatedInformationFromJson; - -exports.message = {}; - -/** - * @param {string | undefined} tsConfigFileName - * @param {import("gulp-typescript").Settings} settings - * @param {Object} options - * @param {string} [options.typescript] - * @returns {CreateProjectMessage} - * - * @typedef CreateProjectMessage - * @property {"createProject"} method - * @property {CreateProjectParams} params - * - * @typedef CreateProjectParams - * @property {string | undefined} tsConfigFileName - * @property {import("gulp-typescript").Settings} settings - * @property {CreateProjectOptions} options - * - * @typedef CreateProjectOptions - * @property {string} [typescript] - */ -exports.message.createProject = function(tsConfigFileName, settings, options) { - return { method: "createProject", params: { tsConfigFileName, settings, options } }; -}; - -/** - * @param {File} file - * @returns {WriteMessage} - * - * @typedef WriteMessage - * @property {"write"} method - * @property {*} params - */ -exports.message.write = function(file) { - return { method: "write", params: vinylToJson(file) }; -}; - -/** - * @returns {FinalMessage} - * - * @typedef FinalMessage - * @property {"final"} method - */ -exports.message.final = function() { - return { method: "final" }; -}; - -/** - * @param {Error} error - * @returns {ErrorMessage} - * - * @typedef ErrorMessage - * @property {"error"} method - * @property {*} params - */ -exports.message.error = function(error) { - return { method: "error", params: errorToJson(error) }; -}; - -exports.message.reporter = {}; - -/** - * @param {TypeScriptError} error - * @returns {reporter.ErrorMessage} - * - * @typedef reporter.ErrorMessage - * @property {"reporter.error"} method - * @property {*} params - */ -exports.message.reporter.error = function(error) { - return { method: "reporter.error", params: typeScriptErrorToJson(error) }; -}; - -/** - * @param {*} results - * @returns {reporter.FinishMessage} - * - * @typedef reporter.FinishMessage - * @property {"reporter.finish"} method - * @property {*} params - */ -exports.message.reporter.finish = function(results) { - return { method: "reporter.finish", params: results }; -}; - -/** - * @typedef {import("vinyl")} File - * @typedef {typeof import("typescript")} TypeScriptModule - * @typedef {import("typescript").SourceFile} SourceFile - * @typedef {import("typescript").Diagnostic} Diagnostic - * @typedef {import("typescript").DiagnosticRelatedInformation} DiagnosticRelatedInformation - * @typedef {import("gulp-typescript").reporter.TypeScriptError} TypeScriptError - * @typedef {WriteMessage | FinalMessage | CreateProjectMessage} HostMessage - * @typedef {WriteMessage | FinalMessage | ErrorMessage | reporter.ErrorMessage | reporter.FinishMessage} WorkerMessage - * - * @typedef SourceFileHost - * @property {(fileName: string) => SourceFile | undefined} getSourceFile - * @property {(fileName: string, text: string, languageVersion: number) => SourceFile} createSourceFile - * - * @typedef VinylHost - * @property {(path: string) => File | undefined} getVinyl - */ -void 0; \ No newline at end of file diff --git a/scripts/build/gulp-typescript-oop/worker.js b/scripts/build/gulp-typescript-oop/worker.js deleted file mode 100644 index 5d68bc591e8..00000000000 --- a/scripts/build/gulp-typescript-oop/worker.js +++ /dev/null @@ -1,79 +0,0 @@ -// @ts-check -const fs = require("fs"); -const tsc = require("gulp-typescript"); -const { Readable, Writable } = require("stream"); -const protocol = require("./protocol"); - -/** @type {tsc.Project} */ -let project; - -/** @type {Readable} */ -let inputStream; - -/** @type {Writable} */ -let outputStream; - -/** @type {tsc.CompileStream} */ -let compileStream; - -process.on("message", (/**@type {protocol.HostMessage}*/ message) => { - try { - switch (message.method) { - case "createProject": { - const { tsConfigFileName, settings, options } = message.params; - if (options.typescript) { - settings.typescript = require(options.typescript); - } - - project = tsConfigFileName === undefined - ? tsc.createProject(settings) - : tsc.createProject(tsConfigFileName, settings); - - inputStream = new Readable({ - objectMode: true, - read() {} - }); - - outputStream = new Writable({ - objectMode: true, - /** - * @param {*} file - */ - write(file, _, callback) { - process.send(protocol.message.write(file)); - callback(); - }, - final(callback) { - process.send(protocol.message.final()); - callback(); - } - }); - compileStream = project({ - error(error) { process.send(protocol.message.reporter.error(error)); }, - finish(results) { process.send(protocol.message.reporter.finish(results)); } - }); - compileStream.on("error", error => { - process.send(protocol.message.error(error)); - }); - outputStream.on("error", () => { - /* do nothing */ - }); - inputStream.pipe(compileStream).pipe(outputStream); - break; - } - case "write": { - const file = protocol.vinylFromJson(message.params); - if (!file.isBuffer()) file.contents = fs.readFileSync(file.path); - inputStream.push(file); - break; - } - case "final": { - inputStream.push(null); - break; - } - } - } - catch (e) { - process.send(protocol.message.error(e)); - } -}); \ No newline at end of file diff --git a/scripts/build/gulp.js b/scripts/build/gulp.js deleted file mode 100644 index 40f27b9526e..00000000000 --- a/scripts/build/gulp.js +++ /dev/null @@ -1,8 +0,0 @@ -// @ts-check -/** - * @typedef {import("gulp").Gulp} Gulp - * @typedef {import("gulp-help").GulpHelp} GulpHelp - * @typedef {GulpHelp & { Gulp: new () => Gulp }} DotGulpModule - * @type {DotGulpModule} - */ -module.exports = require("gulp-help")(require("gulp")); \ No newline at end of file diff --git a/scripts/build/lib.js b/scripts/build/lib.js deleted file mode 100644 index f8dac7529e0..00000000000 --- a/scripts/build/lib.js +++ /dev/null @@ -1,30 +0,0 @@ -// @ts-check -const readJson = require("./readJson"); -const path = require("path"); -const gulp = require("./gulp"); -const newer = require("gulp-newer"); -const concat = require("gulp-concat"); -const merge2 = require("merge2"); - -/** @type {{ libs: string[], paths?: Record, sources?: Record }} */ -const libraries = readJson("./src/lib/libs.json"); -const libs = libraries.libs.map(lib => { - const relativeSources = ["header.d.ts"].concat(libraries.sources && libraries.sources[lib] || [lib + ".d.ts"]); - const relativeTarget = libraries.paths && libraries.paths[lib] || ("lib." + lib + ".d.ts"); - const sources = relativeSources.map(s => path.posix.join("src/lib", s)); - const target = `built/local/${relativeTarget}`; - return { target, relativeTarget, sources }; -}); -exports.libraryTargets = libs.map(lib => lib.target); - -/** - * @param {string[]} prepends - */ -function generateLibs(prepends) { - return merge2(libs.map(({ sources, target, relativeTarget }) => - gulp.src(prepends.concat(sources)) - .pipe(newer(target)) - .pipe(concat(relativeTarget, { newLine: "\n\n" })) - .pipe(gulp.dest("built/local")))); -} -exports.generateLibs = generateLibs; \ No newline at end of file diff --git a/scripts/build/mkdirp.js b/scripts/build/mkdirp.js deleted file mode 100644 index 8a918a387d4..00000000000 --- a/scripts/build/mkdirp.js +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-check -const mkdirp = require("mkdirp"); - -module.exports = exports = mkdirpAsync; - -/** - * @param {string} dir - * @param {mkdirp.Mode | mkdirp.Options} [opts] - */ -function mkdirpAsync(dir, opts) { - return new Promise((resolve, reject) => mkdirp(dir, opts, (err, made) => err ? reject(err) : resolve(made))); -} - -exports.sync = mkdirp.sync; \ No newline at end of file diff --git a/scripts/build/needsUpdate.js b/scripts/build/needsUpdate.js deleted file mode 100644 index 436003eab87..00000000000 --- a/scripts/build/needsUpdate.js +++ /dev/null @@ -1,72 +0,0 @@ -// @ts-check -const fs = require("fs"); - -module.exports = needsUpdate; - -/** - * @param {string | string[]} source - * @param {string | string[]} dest - * @returns {boolean} - */ -function needsUpdate(source, dest) { - if (typeof source === "string" && typeof dest === "string") { - if (fs.existsSync(dest)) { - const {mtime: outTime} = fs.statSync(dest); - const {mtime: inTime} = fs.statSync(source); - if (+inTime <= +outTime) { - return false; - } - } - } - else if (typeof source === "string" && typeof dest !== "string") { - const {mtime: inTime} = fs.statSync(source); - for (const filepath of dest) { - if (fs.existsSync(filepath)) { - const {mtime: outTime} = fs.statSync(filepath); - if (+inTime > +outTime) { - return true; - } - } - else { - return true; - } - } - return false; - } - else if (typeof source !== "string" && typeof dest === "string") { - if (fs.existsSync(dest)) { - const {mtime: outTime} = fs.statSync(dest); - for (const filepath of source) { - if (fs.existsSync(filepath)) { - const {mtime: inTime} = fs.statSync(filepath); - if (+inTime > +outTime) { - return true; - } - } - else { - return true; - } - } - return false; - } - } - else if (typeof source !== "string" && typeof dest !== "string") { - for (let i = 0; i < source.length; i++) { - if (!dest[i]) { - continue; - } - if (fs.existsSync(dest[i])) { - const {mtime: outTime} = fs.statSync(dest[i]); - const {mtime: inTime} = fs.statSync(source[i]); - if (+inTime > +outTime) { - return true; - } - } - else { - return true; - } - } - return false; - } - return true; -} \ No newline at end of file diff --git a/scripts/build/options.js b/scripts/build/options.js index e9e3bfb7b1b..b7233ba70b0 100644 --- a/scripts/build/options.js +++ b/scripts/build/options.js @@ -4,7 +4,7 @@ const os = require("os"); /** @type {CommandLineOptions} */ module.exports = minimist(process.argv.slice(2), { - boolean: ["debug", "dirty", "inspect", "light", "colors", "lint", "lkg", "soft", "fix", "failed", "keepFailed"], + boolean: ["debug", "dirty", "inspect", "light", "colors", "lint", "lkg", "soft", "fix", "failed", "keepFailed", "force", "built"], string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout"], alias: { "b": "browser", @@ -15,7 +15,7 @@ module.exports = minimist(process.argv.slice(2), { "r": "reporter", "c": "colors", "color": "colors", "w": "workers", - "f": "fix", + "f": "fix" }, default: { soft: false, @@ -34,11 +34,16 @@ module.exports = minimist(process.argv.slice(2), { workers: process.env.workerCount || os.cpus().length, failed: false, keepFailed: false, - lkg: false, - dirty: false + lkg: true, + dirty: false, + built: false } }); +if (module.exports.built) { + module.exports.lkg = false; +} + /** * @typedef TypedOptions * @property {boolean} debug @@ -48,6 +53,7 @@ module.exports = minimist(process.argv.slice(2), { * @property {boolean} colors * @property {boolean} lint * @property {boolean} lkg + * @property {boolean} built * @property {boolean} soft * @property {boolean} fix * @property {string} browser diff --git a/scripts/build/prepend.js b/scripts/build/prepend.js index 6e7b794e79b..51c8aa84a8a 100644 --- a/scripts/build/prepend.js +++ b/scripts/build/prepend.js @@ -1,20 +1,18 @@ // @ts-check const stream = require("stream"); -const Vinyl = require("./vinyl"); +const Vinyl = require("vinyl"); const ts = require("../../lib/typescript"); const fs = require("fs"); const { base64VLQFormatEncode } = require("./sourcemaps"); -module.exports = exports = prepend; - /** - * @param {string | ((file: Vinyl) => string)} data + * @param {string | ((file: import("vinyl")) => string)} data */ function prepend(data) { return new stream.Transform({ objectMode: true, /** - * @param {string | Buffer | Vinyl} input + * @param {string | Buffer | import("vinyl")} input * @param {(error: Error, data?: any) => void} cb */ transform(input, _, cb) { @@ -56,11 +54,11 @@ function prepend(data) { exports.prepend = prepend; /** - * @param {string | ((file: Vinyl) => string)} file + * @param {string | ((file: import("vinyl")) => string)} file */ function prependFile(file) { const data = typeof file === "string" ? fs.readFileSync(file, "utf8") : vinyl => fs.readFileSync(file(vinyl), "utf8"); return prepend(data) } -exports.file = prependFile; \ No newline at end of file +exports.prependFile = prependFile; \ No newline at end of file diff --git a/scripts/build/project.js b/scripts/build/project.js deleted file mode 100644 index 6c1ac0f3e04..00000000000 --- a/scripts/build/project.js +++ /dev/null @@ -1,1095 +0,0 @@ -// @ts-check -const path = require("path"); -const fs = require("fs"); -const gulp = require("./gulp"); -const gulpif = require("gulp-if"); -const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util) -const chalk = require("./chalk"); -const sourcemaps = require("gulp-sourcemaps"); -const merge2 = require("merge2"); -const tsc = require("gulp-typescript"); -const tsc_oop = require("./gulp-typescript-oop"); -const upToDate = require("./upToDate"); -const ts = require("../../lib/typescript"); -const del = require("del"); -const needsUpdate = require("./needsUpdate"); -const mkdirp = require("./mkdirp"); -const prettyTime = require("pretty-hrtime"); -const { reportDiagnostics } = require("./diagnostics"); -const { CountdownEvent, ManualResetEvent } = require("prex"); - -const workStartedEvent = new ManualResetEvent(); -const countdown = new CountdownEvent(0); - -class CompilationGulp extends gulp.Gulp { - /** - * @param {boolean} [verbose] - */ - fork(verbose) { - const child = new ForkedGulp(this.tasks); - child.on("task_start", e => { - if (countdown.remainingCount === 0) { - countdown.reset(1); - workStartedEvent.set(); - workStartedEvent.reset(); - } - else { - countdown.add(); - } - if (verbose) { - log('Starting', `'${chalk.cyan(e.task)}' ${chalk.gray(`(${countdown.remainingCount} remaining)`)}...`); - } - }); - child.on("task_stop", e => { - countdown.signal(); - if (verbose) { - log('Finished', `'${chalk.cyan(e.task)}' after ${chalk.magenta(prettyTime(/** @type {*}*/(e).hrDuration))} ${chalk.gray(`(${countdown.remainingCount} remaining)`)}`); - } - }); - child.on("task_err", e => { - countdown.signal(); - if (verbose) { - log(`'${chalk.cyan(e.task)}' ${chalk.red("errored after")} ${chalk.magenta(prettyTime(/** @type {*}*/(e).hrDuration))} ${chalk.gray(`(${countdown.remainingCount} remaining)`)}`); - log(e.err ? e.err.stack : e.message); - } - }); - return child; - } - - // @ts-ignore - start() { - throw new Error("Not supported, use fork."); - } -} - -class ForkedGulp extends gulp.Gulp { - /** - * @param {gulp.Gulp["tasks"]} tasks - */ - constructor(tasks) { - super(); - this.tasks = tasks; - } - - // Do not reset tasks - _resetAllTasks() {} - _resetSpecificTasks() {} - _resetTask() {} -} - -// internal `Gulp` instance for compilation artifacts. -const compilationGulp = new CompilationGulp(); - -/** @type {Map} */ -const projectGraphCache = new Map(); - -/** @type {Map} */ -const typescriptAliasMap = new Map(); - -/** - * Defines a gulp orchestration for a TypeScript project, returning a callback that can be used to trigger compilation. - * @param {string} projectSpec The path to a tsconfig.json file or its containing directory. - * @param {CompileOptions} [options] Project compilation options. - * @returns {() => Promise} - */ -function createCompiler(projectSpec, options) { - const resolvedOptions = resolveCompileOptions(options); - const resolvedProjectSpec = resolveProjectSpec(projectSpec, resolvedOptions.paths, /*referrer*/ undefined); - const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths); - projectGraph.isRoot = true; - const taskName = compileTaskName(ensureCompileTask(projectGraph, resolvedOptions), resolvedOptions.typescript); - return () => new Promise((resolve, reject) => compilationGulp - .fork(resolvedOptions.verbose) - .start(taskName, err => err ? reject(err) : resolve())); -} -exports.createCompiler = createCompiler; - -/** - * Defines and executes a gulp orchestration for a TypeScript project. - * @param {string} projectSpec The path to a tsconfig.json file or its containing directory. - * @param {CompileOptions} [options] Project compilation options. - * @returns {Promise} - * - * @typedef CompileOptions - * @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`. - * @property {string} [base] The path to use as the base for relative paths. Defaults to `cwd`. - * @property {string} [typescript] A module specifier or path (relative to gulpfile.js) to the version of TypeScript to use. - * @property {Hook} [js] Pipeline hook for .js file outputs. - * @property {Hook} [dts] Pipeline hook for .d.ts file outputs. - * @property {boolean} [verbose] Indicates whether verbose logging is enabled. - * @property {boolean} [force] Force recompilation (no up-to-date check). - * @property {boolean} [inProcess] Indicates whether to run gulp-typescript in-process or out-of-process (default). - * - * @typedef {(stream: NodeJS.ReadableStream) => NodeJS.ReadWriteStream} Hook - */ -function compile(projectSpec, options) { - const compiler = createCompiler(projectSpec, options); - return compiler(); -} -exports.compile = compile; - -/** - * Defines a gulp orchestration to clean the outputs of a TypeScript project, returning a callback that can be used to trigger compilation. - * @param {string} projectSpec The path to a tsconfig.json file or its containing directory. - * @param {PathOptions} [options] Project clean options. - */ -function createCleaner(projectSpec, options) { - const paths = resolvePathOptions(options); - const resolvedProjectSpec = resolveProjectSpec(projectSpec, paths, /*referrer*/ undefined); - const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, paths); - projectGraph.isRoot = true; - const taskName = cleanTaskName(ensureCleanTask(projectGraph)); - return () => new Promise((resolve, reject) => compilationGulp - .fork() - .start(taskName, err => err ? reject(err) : resolve())); -} -exports.createCleaner = createCleaner; - -/** - * Defines and executes a gulp orchestration to clean the outputs of a TypeScript project. - * @param {string} projectSpec The path to a tsconfig.json file or its containing directory. - * @param {PathOptions} [options] Project clean options. - */ -function clean(projectSpec, options) { - const cleaner = createCleaner(projectSpec, options); - return cleaner(); -} -exports.clean = clean; - -/** - * Defines a watcher to execute a gulp orchestration to recompile a TypeScript project. - * @param {string} projectSpec - * @param {WatchCallback | string[] | CompileOptions} [options] - * @param {WatchCallback | string[]} [tasks] - * @param {WatchCallback} [callback] - */ -function watch(projectSpec, options, tasks, callback) { - if (typeof tasks === "function") callback = tasks, tasks = /**@type {string[] | undefined}*/(undefined); - if (typeof options === "function") callback = options, tasks = /**@type {string[] | undefined}*/(undefined), options = /**@type {CompileOptions | undefined}*/(undefined); - if (Array.isArray(options)) tasks = options, options = /**@type {CompileOptions | undefined}*/(undefined); - const resolvedOptions = resolveCompileOptions(options); - resolvedOptions.watch = true; - const resolvedProjectSpec = resolveProjectSpec(projectSpec, resolvedOptions.paths, /*referrer*/ undefined); - const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths); - projectGraph.isRoot = true; - ensureWatcher(projectGraph, resolvedOptions, tasks, callback); -} -exports.watch = watch; - -/** - * Adds a named alias for a TypeScript language service path - * @param {string} alias An alias for a TypeScript version. - * @param {string} typescript An alias or module specifier for a TypeScript version. - * @param {PathOptions} [options] Options used to resolve the path to `typescript`. - */ -function addTypeScript(alias, typescript, options) { - const paths = resolvePathOptions(options); - typescriptAliasMap.set(alias, { typescript, alias, paths }); -} -exports.addTypeScript = addTypeScript; - -/** - * Flattens a project with project references into a single project. - * @param {string} projectSpec The path to a tsconfig.json file or its containing directory. - * @param {string} flattenedProjectSpec The output path for the flattened tsconfig.json file. - * @param {FlattenOptions} [options] Options used to flatten a project hierarchy. - * - * @typedef FlattenOptions - * @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`. - * @property {CompilerOptions} [compilerOptions] Compiler option overrides. - * @property {boolean} [force] Forces creation of the output project. - * @property {string[]} [exclude] Files to exclude (relative to `cwd`) - */ -function flatten(projectSpec, flattenedProjectSpec, options = {}) { - const paths = resolvePathOptions(options); - const files = []; - const resolvedOutputSpec = path.resolve(paths.cwd, flattenedProjectSpec); - const resolvedOutputDirectory = path.dirname(resolvedOutputSpec); - const resolvedProjectSpec = resolveProjectSpec(projectSpec, paths, /*referrer*/ undefined); - const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, paths); - const skipProjects = /**@type {Set}*/(new Set()); - const skipFiles = new Set(options && options.exclude && options.exclude.map(file => path.resolve(paths.cwd, file))); - recur(projectGraph); - - if (options.force || needsUpdate(files, resolvedOutputSpec)) { - const config = { - extends: normalizeSlashes(path.relative(resolvedOutputDirectory, resolvedProjectSpec)), - compilerOptions: options.compilerOptions || {}, - files: files.map(file => normalizeSlashes(path.relative(resolvedOutputDirectory, file))) - }; - mkdirp.sync(resolvedOutputDirectory); - fs.writeFileSync(resolvedOutputSpec, JSON.stringify(config, undefined, 2), "utf8"); - } - - /** - * @param {ProjectGraph} projectGraph - */ - function recur(projectGraph) { - if (skipProjects.has(projectGraph)) return; - skipProjects.add(projectGraph); - for (const ref of projectGraph.references) { - recur(ref.target); - } - for (let file of projectGraph.project.fileNames) { - file = path.resolve(projectGraph.projectDirectory, file); - if (skipFiles.has(file)) continue; - skipFiles.add(file); - files.push(file); - } - } -} -exports.flatten = flatten; - -/** - * Returns a Promise that resolves when all pending build tasks have completed - * @param {import("prex").CancellationToken} [token] - */ -function waitForWorkToComplete(token) { - return countdown.wait(token); -} -exports.waitForWorkToComplete = waitForWorkToComplete; - -/** - * Returns a Promise that resolves when all pending build tasks have completed - * @param {import("prex").CancellationToken} [token] - */ -function waitForWorkToStart(token) { - return workStartedEvent.wait(token); -} -exports.waitForWorkToStart = waitForWorkToStart; - -function getRemainingWork() { - return countdown.remainingCount > 0; -} -exports.hasRemainingWork = getRemainingWork; - -/** - * Resolve a TypeScript specifier into a fully-qualified module specifier and any requisite dependencies. - * @param {string} typescript An unresolved module specifier to a TypeScript version. - * @param {ResolvedPathOptions} paths Paths used to resolve `typescript`. - * @returns {ResolvedTypeScript} - * - * @typedef {string & {_isResolvedTypeScript: never}} ResolvedTypeScriptSpec - * - * @typedef ResolvedTypeScript - * @property {ResolvedTypeScriptSpec} typescript - * @property {string} [alias] - */ -function resolveTypeScript(typescript = "default", paths) { - let alias; - while (typescriptAliasMap.has(typescript)) { - ({ typescript, alias, paths } = typescriptAliasMap.get(typescript)); - } - - if (typescript === "default") { - typescript = require.resolve("../../lib/typescript"); - } - else if (isPath(typescript)) { - typescript = path.resolve(paths.cwd, typescript); - } - - return { typescript: /**@type {ResolvedTypeScriptSpec}*/(normalizeSlashes(typescript)), alias }; -} - -/** - * Gets a suffix to append to Gulp task names that vary by TypeScript version. - * @param {ResolvedTypeScript} typescript A resolved module specifier to a TypeScript version. - * @param {ResolvedPathOptions} paths Paths used to resolve a relative reference to `typescript`. - */ -function getTaskNameSuffix(typescript, paths) { - return typescript.typescript === resolveTypeScript("default", paths).typescript ? "" : - typescript.alias ? `@${typescript.alias}` : - isPath(typescript.typescript) ? `@${normalizeSlashes(path.relative(paths.base, typescript.typescript))}` : - `@${typescript}`; -} - -/** @type {ResolvedPathOptions} */ -const defaultPaths = (() => { - const cwd = /**@type {AbsolutePath}*/(normalizeSlashes(process.cwd())); - return { cwd, base: cwd }; -})(); - -/** - * @param {PathOptions | undefined} options Path options to resolve and normalize. - * @returns {ResolvedPathOptions} - * - * @typedef PathOptions - * @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`. - * @property {string} [base] The path to use as the base for relative paths. Defaults to `cwd`. - * - * @typedef ResolvedPathOptions - * @property {AbsolutePath} cwd The path to use for the current working directory. Defaults to `process.cwd()`. - * @property {AbsolutePath} base The path to use as the base for relative paths. Defaults to `cwd`. - */ -function resolvePathOptions(options) { - const cwd = options && options.cwd ? resolvePath(defaultPaths.cwd, options.cwd) : defaultPaths.cwd; - const base = options && options.base ? resolvePath(cwd, options.base) : cwd; - return cwd === defaultPaths.cwd && base === defaultPaths.base ? defaultPaths : { cwd, base }; -} - -/** - * @param {CompileOptions} [options] - * @returns {ResolvedCompileOptions} - * - * @typedef ResolvedCompileOptions - * @property {ResolvedPathOptions} paths - * @property {ResolvedTypeScript} typescript A resolved reference to a TypeScript implementation. - * @property {Hook} [js] Pipeline hook for .js file outputs. - * @property {Hook} [dts] Pipeline hook for .d.ts file outputs. - * @property {boolean} [verbose] Indicates whether verbose logging is enabled. - * @property {boolean} [force] Force recompilation (no up-to-date check). - * @property {boolean} [inProcess] Indicates whether to run gulp-typescript in-process or out-of-process (default). - * @property {boolean} [watch] Indicates the project was created in watch mode - */ -function resolveCompileOptions(options = {}) { - const paths = resolvePathOptions(options); - const typescript = resolveTypeScript(options.typescript, paths); - return { - paths, - typescript, - js: options.js, - dts: options.dts, - verbose: options.verbose || false, - force: options.force || false, - inProcess: options.inProcess || false - }; -} - -/** - * @param {ResolvedCompileOptions} left - * @param {ResolvedCompileOptions} right - * @returns {ResolvedCompileOptions} - */ -function mergeCompileOptions(left, right) { - if (left.typescript.typescript !== right.typescript.typescript) throw new Error("Cannot merge project options targeting different TypeScript packages"); - if (tryReuseCompileOptions(left, right)) return left; - return { - paths: left.paths, - typescript: left.typescript, - js: right.js || left.js, - dts: right.dts || left.dts, - verbose: right.verbose || left.verbose, - force: right.force || left.force, - inProcess: right.inProcess || left.inProcess, - watch: right.watch || left.watch - }; -} - -/** - * @param {ResolvedCompileOptions} left - * @param {ResolvedCompileOptions} right - */ -function tryReuseCompileOptions(left, right) { - return left === right - || left.js === (right.js || left.js) - && left.dts === (right.dts || left.dts) - && !left.verbose === !(right.verbose || left.verbose) - && !left.force === !(right.force || left.force) - && !left.inProcess === !(right.inProcess || left.inProcess); -} - -/** - * @param {ResolvedProjectSpec} projectSpec - * @param {ResolvedPathOptions} paths - * @returns {UnqualifiedProjectName} - * - * @typedef {string & {_isUnqualifiedProjectName:never}} UnqualifiedProjectName - */ -function getUnqualifiedProjectName(projectSpec, paths) { - let projectName = path.relative(paths.base, projectSpec); - if (path.basename(projectName) === "tsconfig.json") projectName = path.dirname(projectName); - return /**@type {UnqualifiedProjectName}*/(normalizeSlashes(projectName)); -} - -/** - * @param {UnqualifiedProjectName} projectName - * @param {ResolvedPathOptions} paths - * @param {ResolvedTypeScript} typescript - * @returns {QualifiedProjectName} - * - * @typedef {string & {_isQualifiedProjectName:never}} QualifiedProjectName - */ -function getQualifiedProjectName(projectName, paths, typescript) { - return /**@type {QualifiedProjectName}*/(projectName + getTaskNameSuffix(typescript, paths)); -} - -/** - * @typedef {import("../../lib/typescript").ParseConfigFileHost} ParseConfigFileHost - * @type {ParseConfigFileHost} - */ -const parseConfigFileHost = { - useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames, - fileExists: fileName => ts.sys.fileExists(fileName), - readFile: fileName => ts.sys.readFile(fileName), - getCurrentDirectory: () => process.cwd(), - readDirectory: (rootDir, extensions, exclude, include, depth) => ts.sys.readDirectory(rootDir, extensions, exclude, include, depth), - onUnRecoverableConfigFileDiagnostic: diagnostic => reportDiagnostics([diagnostic]) -}; - -/** - * @param {AbsolutePath} [cwd] - * @returns {ParseConfigFileHost} - */ -function getParseConfigFileHost(cwd) { - if (!cwd || cwd === defaultPaths.cwd) return parseConfigFileHost; - return { - useCaseSensitiveFileNames: parseConfigFileHost.useCaseSensitiveFileNames, - fileExists: parseConfigFileHost.fileExists, - readFile: parseConfigFileHost.readFile, - getCurrentDirectory: () => cwd, - readDirectory: parseConfigFileHost.readDirectory, - onUnRecoverableConfigFileDiagnostic: diagnostic => reportDiagnostics([diagnostic], { cwd }) - }; -} - -/** - * @param {ResolvedProjectSpec} projectSpec - * @param {ResolvedPathOptions} paths - * @returns {ProjectGraph} - * - * @typedef ProjectGraph - * @property {ResolvedPathOptions} paths - * @property {ResolvedProjectSpec} projectSpec The fully qualified path to the tsconfig.json of the project - * @property {UnqualifiedProjectName} projectName The relative project name, excluding any TypeScript suffix. - * @property {AbsolutePath} projectDirectory The fully qualified path to the project directory. - * @property {ParsedCommandLine} project The parsed tsconfig.json file. - * @property {ProjectGraphReference[]} references An array of project references. - * @property {Set} referrers An array of referring projects. - * @property {Set} inputs A set of compilation inputs. - * @property {Set} outputs A set of compilation outputs. - * @property {Map} configurations TypeScript-specific configurations for the project. - * @property {boolean} cleanTaskCreated A value indicating whether a `clean:` task has been created for this project (not dependent on TypeScript version). - * @property {boolean} watcherCreated A value indicating whether a watcher has been created for this project. - * @property {boolean} isRoot The project graph is a root project reference. - * @property {Set} [allWatchers] Tasks to execute when the compilation has completed after being triggered by a watcher. - * - * @typedef ProjectGraphReference - * @property {ProjectGraph} source The referring project. - * @property {ProjectGraph} target The referenced project. - */ -function getOrCreateProjectGraph(projectSpec, paths) { - let projectGraph = projectGraphCache.get(projectSpec); - if (!projectGraph) { - const project = parseProject(projectSpec, paths); - const projectDirectory = parentDirectory(projectSpec); - projectGraph = { - paths, - projectSpec, - projectName: getUnqualifiedProjectName(projectSpec, paths), - projectDirectory, - project, - references: [], - referrers: new Set(), - inputs: new Set(project.fileNames.map(file => resolvePath(projectDirectory, file))), - outputs: new Set(ts.getAllProjectOutputs(project).map(file => resolvePath(projectDirectory, file))), - configurations: new Map(), - cleanTaskCreated: false, - watcherCreated: false, - isRoot: false - }; - projectGraphCache.set(projectSpec, projectGraph); - if (project.projectReferences) { - for (const projectReference of project.projectReferences) { - const resolvedProjectSpec = resolveProjectSpec(projectReference.path, paths, projectGraph); - const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, paths); - const reference = { source: projectGraph, target: referencedProject }; - projectGraph.references.push(reference); - referencedProject.referrers.add(projectGraph); - } - } - } - return projectGraph; -} - -/** - * @param {ResolvedPathOptions} paths - */ -function createParseProject(paths) { - /** - * @param {string} configFilePath - */ - function getProject(configFilePath) { - const projectSpec = resolveProjectSpec(configFilePath, paths, /*referrer*/ undefined); - const projectGraph = getOrCreateProjectGraph(projectSpec, defaultPaths); - return projectGraph && projectGraph.project; - } - return getProject; -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ParsedCommandLine} parsedProject - */ -function updateProjectGraph(projectGraph, parsedProject) { - projectGraph.project = parsedProject; - projectGraph.inputs = new Set(projectGraph.project.fileNames.map(file => resolvePath(projectGraph.projectDirectory, file))); - projectGraph.outputs = new Set(ts.getAllProjectOutputs(projectGraph.project).map(file => resolvePath(projectGraph.projectDirectory, file))); - - // Update project references. - const oldReferences = new Set(projectGraph.references.map(ref => ref.target)); - projectGraph.references = []; - if (projectGraph.project.projectReferences) { - for (const projectReference of projectGraph.project.projectReferences) { - const resolvedProjectSpec = resolveProjectSpec(projectReference.path, projectGraph.paths, projectGraph); - const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, projectGraph.paths); - const reference = { source: projectGraph, target: referencedProject }; - projectGraph.references.push(reference); - referencedProject.referrers.add(projectGraph); - oldReferences.delete(referencedProject); - } - } - - // Remove project references that have been removed from the project - for (const referencedProject of oldReferences) { - referencedProject.referrers.delete(projectGraph); - // If there are no more references to this project and the project was not directly requested, - // remove it from the cache. - if (referencedProject.referrers.size === 0 && !referencedProject.isRoot) { - projectGraphCache.delete(referencedProject.projectSpec); - } - } -} - -/** - * @param {ResolvedProjectSpec} projectSpec - * @param {ResolvedPathOptions} paths - */ -function parseProject(projectSpec, paths) { - return ts.getParsedCommandLineOfConfigFile(projectSpec, {}, getParseConfigFileHost(paths.cwd)); -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedCompileOptions} resolvedOptions - * @returns {ProjectGraphConfiguration} - * - * @typedef ProjectGraphConfiguration - * @property {QualifiedProjectName} projectName - * @property {ResolvedCompileOptions} resolvedOptions - * @property {boolean} compileTaskCreated A value indicating whether a `compile:` task has been created for this project. - * @property {Set} [watchers] Tasks to execute when the compilation has completed after being triggered by a watcher. - */ -function getOrCreateProjectGraphConfiguration(projectGraph, resolvedOptions) { - let projectGraphConfig = projectGraph.configurations.get(resolvedOptions.typescript.typescript); - if (!projectGraphConfig) { - projectGraphConfig = { - projectName: getQualifiedProjectName(projectGraph.projectName, resolvedOptions.paths, resolvedOptions.typescript), - resolvedOptions, - compileTaskCreated: false - }; - projectGraph.configurations.set(resolvedOptions.typescript.typescript, projectGraphConfig); - } - return projectGraphConfig; -} - -/** - * Resolves a series of path steps as a normalized, canonical, and absolute path. - * @param {AbsolutePath} basePath - * @param {...string} paths - * @returns {AbsolutePath} - * - * @typedef {string & {_isResolvedPath:never}} AbsolutePath - */ -function resolvePath(basePath, ...paths) { - return /**@type {AbsolutePath}*/(normalizeSlashes(path.resolve(basePath, ...paths))); -} - -/** - * @param {AbsolutePath} from - * @param {AbsolutePath} to - * @returns {Path} - * - * @typedef {string & {_isRelativePath:never}} RelativePath - * @typedef {RelativePath | AbsolutePath} Path - */ -function relativePath(from, to) { - let relativePath = normalizeSlashes(path.relative(from, to)); - if (!relativePath) relativePath = "."; - if (path.isAbsolute(relativePath)) return /**@type {AbsolutePath}*/(relativePath); - if (relativePath.charAt(0) !== ".") relativePath = "./" + relativePath; - return /**@type {RelativePath}*/(relativePath); -} - -/** - * @param {AbsolutePath} file - * @returns {AbsolutePath} - */ -function parentDirectory(file) { - const dirname = path.dirname(file); - if (!dirname || dirname === file) return file; - return /**@type {AbsolutePath}*/(normalizeSlashes(dirname)); -} - -/** - * @param {string} projectSpec - * @param {ResolvedPathOptions} paths - * @param {ProjectGraph | undefined} referrer - * @returns {ResolvedProjectSpec} - * - * @typedef {AbsolutePath & {_isResolvedProjectSpec: never}} ResolvedProjectSpec - */ -function resolveProjectSpec(projectSpec, paths, referrer) { - let projectPath = resolvePath(paths.cwd, referrer && referrer.projectDirectory || "", projectSpec); - if (!ts.sys.fileExists(projectPath)) projectPath = resolvePath(paths.cwd, projectPath, "tsconfig.json"); - return /**@type {ResolvedProjectSpec}*/(normalizeSlashes(projectPath)); -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedPathOptions} paths - */ -function resolveDestPath(projectGraph, paths) { - /** @type {AbsolutePath} */ - let destPath = projectGraph.projectDirectory; - if (projectGraph.project.options.outDir) { - destPath = resolvePath(paths.cwd, destPath, projectGraph.project.options.outDir); - } - else if (projectGraph.project.options.outFile || projectGraph.project.options.out) { - destPath = parentDirectory(resolvePath(paths.cwd, destPath, projectGraph.project.options.outFile || projectGraph.project.options.out)); - } - return relativePath(paths.base, destPath); -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedCompileOptions} options - */ -function ensureCompileTask(projectGraph, options) { - const projectGraphConfig = getOrCreateProjectGraphConfiguration(projectGraph, options); - projectGraphConfig.resolvedOptions = mergeCompileOptions(projectGraphConfig.resolvedOptions, options); - const hasCompileTask = projectGraphConfig.compileTaskCreated; - projectGraphConfig.compileTaskCreated = true; - const deps = makeProjectReferenceCompileTasks(projectGraph, projectGraphConfig.resolvedOptions.typescript, projectGraphConfig.resolvedOptions.paths, projectGraphConfig.resolvedOptions.watch); - if (!hasCompileTask) { - compilationGulp.task(compileTaskName(projectGraph, projectGraphConfig.resolvedOptions.typescript), deps, () => { - const destPath = resolveDestPath(projectGraph, projectGraphConfig.resolvedOptions.paths); - const { sourceMap, inlineSourceMap, inlineSources = false, sourceRoot, declarationMap } = projectGraph.project.options; - const configFilePath = projectGraph.project.options.configFilePath; - const sourceMapPath = inlineSourceMap ? undefined : "."; - const sourceMapOptions = { includeContent: inlineSources, sourceRoot, destPath }; - const project = projectGraphConfig.resolvedOptions.inProcess - ? tsc.createProject(configFilePath, { typescript: require(projectGraphConfig.resolvedOptions.typescript.typescript) }) - : tsc_oop.createProject(configFilePath, {}, { typescript: projectGraphConfig.resolvedOptions.typescript.typescript }); - const stream = project.src() - .pipe(gulpif(!projectGraphConfig.resolvedOptions.force, upToDate(projectGraph.project, { verbose: projectGraphConfig.resolvedOptions.verbose, parseProject: createParseProject(projectGraphConfig.resolvedOptions.paths) }))) - .pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.init())) - .pipe(project()); - if (projectGraphConfig.resolvedOptions.watch) { - stream.on("error", error => { - if (error.message === "TypeScript: Compilation failed") { - stream.emit("end"); - stream.js.emit("end"); - stream.dts.emit("end"); - } - }); - } - - const additionalJsOutputs = projectGraphConfig.resolvedOptions.js ? projectGraphConfig.resolvedOptions.js(stream.js) - .pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions))) : undefined; - const additionalDtsOutputs = projectGraphConfig.resolvedOptions.dts ? projectGraphConfig.resolvedOptions.dts(stream.dts) - .pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions))) : undefined; - - const js = stream.js - .pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.write(sourceMapPath, sourceMapOptions))); - const dts = stream.dts - .pipe(gulpif(declarationMap, sourcemaps.write(sourceMapPath, sourceMapOptions))); - return merge2([js, dts, additionalJsOutputs, additionalDtsOutputs].filter(x => !!x)) - .pipe(gulp.dest(destPath)); - }); - } - return projectGraph; -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedTypeScript} typescript - * @param {ResolvedPathOptions} paths - * @param {boolean} watch - */ -function makeProjectReferenceCompileTasks(projectGraph, typescript, paths, watch) { - return projectGraph.references.map(({target}) => compileTaskName(ensureCompileTask(target, { paths, typescript, watch }), typescript)); -} - -/** - * @param {ProjectGraph} projectGraph - */ -function ensureCleanTask(projectGraph) { - if (!projectGraph.cleanTaskCreated) { - const deps = makeProjectReferenceCleanTasks(projectGraph); - compilationGulp.task(cleanTaskName(projectGraph), deps, () => { - let outputs = ts.getAllProjectOutputs(projectGraph.project); - if (!projectGraph.project.options.inlineSourceMap) { - if (projectGraph.project.options.sourceMap) { - outputs = outputs.concat(outputs.filter(file => /\.jsx?$/.test(file)).map(file => file + ".map")); - } - if (projectGraph.project.options.declarationMap) { - outputs = outputs.concat(outputs.filter(file => /\.d.ts$/.test(file)).map(file => file + ".map")); - } - } - return del(outputs); - }); - projectGraph.cleanTaskCreated = true; - } - return projectGraph; -} - -/** - * @param {ProjectGraph} projectGraph - */ -function makeProjectReferenceCleanTasks(projectGraph) { - return projectGraph.references.map(({target}) => cleanTaskName(ensureCleanTask(target))); -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedCompileOptions} options - * @param {string[]} [tasks] - * @param {(err?: any) => void} [callback] - * - * @typedef Watcher - * @property {string[]} [tasks] - * @property {(err?: any) => void} [callback] - * - * @typedef WatcherRegistration - * @property {() => void} end - */ -function ensureWatcher(projectGraph, options, tasks, callback) { - ensureCompileTask(projectGraph, options); - if (!projectGraph.watcherCreated) { - projectGraph.watcherCreated = true; - makeProjectReferenceWatchers(projectGraph, options.typescript, options.paths); - createWatcher(projectGraph, options, () => { - for (const config of projectGraph.configurations.values()) { - const taskName = compileTaskName(projectGraph, config.resolvedOptions.typescript); - const task = compilationGulp.tasks[taskName]; - if (!task) continue; - possiblyTriggerRecompilation(config, task); - } - }); - } - if ((tasks && tasks.length) || callback) { - const projectGraphConfig = getOrCreateProjectGraphConfiguration(projectGraph, options); - if (!projectGraphConfig.watchers) projectGraphConfig.watchers = new Set(); - if (!projectGraph.allWatchers) projectGraph.allWatchers = new Set(); - - /** @type {Watcher} */ - const watcher = { tasks, callback }; - projectGraphConfig.watchers.add(watcher); - projectGraph.allWatchers.add(watcher); - - /** @type {WatcherRegistration} */ - const registration = { - end() { - projectGraphConfig.watchers.delete(watcher); - projectGraph.allWatchers.delete(watcher); - } - }; - return registration; - } -} - -/** - * @param {ProjectGraphConfiguration} config - * @param {import("orchestrator").Task} task - */ -function possiblyTriggerRecompilation(config, task) { - // if any of the task's dependencies are still running, wait until they are complete. - for (const dep of task.dep) { - if (compilationGulp.tasks[dep].running) { - setTimeout(possiblyTriggerRecompilation, 50, config, task); - return; - } - } - - triggerRecompilation(task, config); -} - -/** - * @param {import("orchestrator").Task} task - * @param {ProjectGraphConfiguration} config - */ -function triggerRecompilation(task, config) { - compilationGulp._resetTask(task); - if (config.watchers && config.watchers.size) { - compilationGulp.fork().start(task.name, () => { - /** @type {Set} */ - const taskNames = new Set(); - /** @type {((err?: any) => void)[]} */ - const callbacks = []; - for (const { tasks, callback } of config.watchers) { - if (tasks) for (const task of tasks) taskNames.add(task); - if (callback) callbacks.push(callback); - } - if (taskNames.size) { - gulp.start([...taskNames], error => { - for (const callback of callbacks) callback(error); - }); - } - else { - for (const callback of callbacks) callback(); - } - }); - } - else { - compilationGulp.fork(/*verbose*/ true).start(task.name); - } -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedTypeScript} typescript - * @param {ResolvedPathOptions} paths - */ -function makeProjectReferenceWatchers(projectGraph, typescript, paths) { - for (const { target } of projectGraph.references) { - ensureWatcher(target, { paths, typescript }); - } -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedCompileOptions} options - * @param {() => void} callback - */ -function createWatcher(projectGraph, options, callback) { - let projectRemoved = false; - let patterns = collectWatcherPatterns(projectGraph.projectSpec, projectGraph.project, projectGraph); - let watcher = /**@type {GulpWatcher}*/ (gulp.watch(patterns, { cwd: projectGraph.projectDirectory }, onWatchEvent)); - - /** - * @param {WatchEvent} event - */ - function onWatchEvent(event) { - const file = resolvePath(options.paths.cwd, event.path); - if (file === projectGraph.projectSpec) { - onProjectWatchEvent(event); - } - else { - onInputOrOutputChanged(file); - } - } - - /** - * @param {WatchEvent} event - */ - function onProjectWatchEvent(event) { - if (event.type === "renamed" || event.type === "deleted") { - onProjectRenamedOrDeleted(); - } - else { - onProjectCreatedOrModified(); - } - } - - function onProjectRenamedOrDeleted() { - // stop listening for file changes and wait for the project to be created again - projectRemoved = true; - watcher.end(); - watcher = /**@type {GulpWatcher}*/ (gulp.watch([projectGraph.projectSpec], onWatchEvent)); - } - - function onProjectCreatedOrModified() { - const newParsedProject = parseProject(projectGraph.projectSpec, options.paths); - const newPatterns = collectWatcherPatterns(projectGraph.projectSpec, newParsedProject, projectGraph); - if (projectRemoved || !sameValues(patterns, newPatterns)) { - projectRemoved = false; - watcher.end(); - updateProjectGraph(projectGraph, newParsedProject); - // Ensure we catch up with any added projects - for (const config of projectGraph.configurations.values()) { - if (config.watchers) { - makeProjectReferenceWatchers(projectGraph, config.resolvedOptions.typescript, config.resolvedOptions.paths); - } - } - patterns = newPatterns; - watcher = /**@type {GulpWatcher}*/ (gulp.watch(patterns, onWatchEvent)); - } - onProjectInvalidated(); - } - - function onProjectInvalidated() { - callback(); - } - - /** - * @param {AbsolutePath} file - */ - function onInputOrOutputChanged(file) { - if (projectGraph.inputs.has(file) || - projectGraph.references.some(ref => ref.target.outputs.has(file))) { - onProjectInvalidated(); - } - } -} - -/** - * @param {ResolvedProjectSpec} projectSpec - * @param {ParsedCommandLine} parsedProject - * @param {ProjectGraph} projectGraph - */ -function collectWatcherPatterns(projectSpec, parsedProject, projectGraph) { - const configFileSpecs = parsedProject.configFileSpecs; - - // NOTE: we do not currently handle files from `/// ` tags - const patterns = /**@type {string[]} */([]); - - // Add the project contents. - if (configFileSpecs) { - addIncludeSpecs(patterns, configFileSpecs.validatedIncludeSpecs); - addExcludeSpecs(patterns, configFileSpecs.validatedExcludeSpecs); - addIncludeSpecs(patterns, configFileSpecs.filesSpecs); - } - else { - addWildcardDirectories(patterns, parsedProject.wildcardDirectories); - addIncludeSpecs(patterns, parsedProject.fileNames); - } - - // Add the project itself. - addIncludeSpec(patterns, projectSpec); - - // TODO: Add the project base. - // addExtendsSpec(patterns, project.raw && project.raw.extends); - - // Add project reference outputs. - addProjectReferences(patterns, parsedProject.projectReferences); - - return patterns; - - /** - * @param {string[]} patterns - * @param {string | undefined} includeSpec - */ - function addIncludeSpec(patterns, includeSpec) { - if (!includeSpec) return; - patterns.push(includeSpec); - } - - /** - * @param {string[]} patterns - * @param {ReadonlyArray | undefined} includeSpecs - */ - function addIncludeSpecs(patterns, includeSpecs) { - if (!includeSpecs) return; - for (const includeSpec of includeSpecs) { - addIncludeSpec(patterns, includeSpec); - } - } - - /** - * @param {string[]} patterns - * @param {string | undefined} excludeSpec - */ - function addExcludeSpec(patterns, excludeSpec) { - if (!excludeSpec) return; - patterns.push("!" + excludeSpec); - } - - /** - * @param {string[]} patterns - * @param {ReadonlyArray | undefined} excludeSpecs - */ - function addExcludeSpecs(patterns, excludeSpecs) { - if (!excludeSpecs) return; - for (const excludeSpec of excludeSpecs) { - addExcludeSpec(patterns, excludeSpec); - } - } - - /** - * @param {string[]} patterns - * @param {ts.MapLike | undefined} wildcardDirectories - */ - function addWildcardDirectories(patterns, wildcardDirectories) { - if (!wildcardDirectories) return; - for (const dirname of Object.keys(wildcardDirectories)) { - const flags = wildcardDirectories[dirname]; - patterns.push(path.join(dirname, flags & ts.WatchDirectoryFlags.Recursive ? "**" : "", "*")); - } - } - - // TODO: Add the project base - // /** - // * @param {string[]} patterns - // * @param {string | undefined} base - // */ - // function addExtendsSpec(patterns, base) { - // if (!base) return; - // addIncludeSpec(patterns, base); - // } - - /** - * @param {string[]} patterns - * @param {ReadonlyArray} projectReferences - */ - function addProjectReferences(patterns, projectReferences) { - if (!projectReferences) return; - for (const projectReference of projectReferences) { - const resolvedProjectSpec = resolveProjectSpec(projectReference.path, projectGraph.paths, projectGraph); - const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, projectGraph.paths); - for (const output of referencedProject.outputs) { - patterns.push(output); - } - } - } -} - -/** - * @param {ProjectGraph} projectGraph - * @param {ResolvedTypeScript} typescript - */ -function compileTaskName(projectGraph, typescript) { - return `compile:${projectGraph.configurations.get(typescript.typescript).projectName}`; -} - -/** - * @param {ProjectGraph} projectGraph - */ -function cleanTaskName(projectGraph) { - return `clean:${projectGraph.projectName}`; -} - -/** - * @param {string} file - */ -function normalizeSlashes(file) { - return file.replace(/\\/g, "/"); -} - -/** - * Determines whether a module specifier is a path - * @param {string} moduleSpec - */ -function isPath(moduleSpec) { - return path.isAbsolute(moduleSpec) || /^\.\.?([\\/]|$)/.test(moduleSpec); -} - -/** - * @template T - * @param {ReadonlyArray} left - * @param {ReadonlyArray} right - */ -function sameValues(left, right) { - if (left === right) return true; - if (left.length !== right.length) return false; - for (let i = 0; i < left.length; i++) { - if (left[i] !== right[i]) return false; - } - return true; -} - -/** - * @typedef {import("../../lib/typescript").ParsedCommandLine & { options: CompilerOptions, configFileSpecs?: ConfigFileSpecs }} ParsedCommandLine - * @typedef {import("../../lib/typescript").CompilerOptions & { configFilePath?: string }} CompilerOptions - * @typedef {import("../../lib/typescript").ProjectReference} ProjectReference - * @typedef {import("gulp").WatchEvent} WatchEvent - * @typedef {import("gulp").WatchCallback} WatchCallback - * @typedef {NodeJS.EventEmitter & { end(): void, add(files: string | string[], done?: () => void): void, remove(file: string): void }} GulpWatcher - * - * @typedef ConfigFileSpecs - * @property {ReadonlyArray | undefined} filesSpecs - * @property {ReadonlyArray | undefined} referenceSpecs - * @property {ReadonlyArray | undefined} validatedIncludeSpecs - * @property {ReadonlyArray | undefined} validatedExcludeSpecs - * @property {ts.MapLike} wildcardDirectories - */ -void 0; \ No newline at end of file diff --git a/scripts/build/projects.js b/scripts/build/projects.js new file mode 100644 index 00000000000..954f37af84e --- /dev/null +++ b/scripts/build/projects.js @@ -0,0 +1,60 @@ +// @ts-check +const { exec, Debouncer } = require("./utils"); + +class ProjectQueue { + /** + * @param {(projects: string[], lkg: boolean, force: boolean) => Promise} action + */ + constructor(action) { + /** @type {{ lkg: boolean, force: boolean, projects?: string[], debouncer: Debouncer }[]} */ + this._debouncers = []; + this._action = action; + } + + /** + * @param {string} project + * @param {object} options + */ + enqueue(project, { lkg = true, force = false } = {}) { + let entry = this._debouncers.find(entry => entry.lkg === lkg && entry.force === force); + if (!entry) { + const debouncer = new Debouncer(100, async () => { + const projects = entry.projects; + if (projects) { + entry.projects = undefined; + await this._action(projects, lkg, force); + } + }); + this._debouncers.push(entry = { lkg, force, debouncer }); + } + if (!entry.projects) entry.projects = []; + entry.projects.push(project); + return entry.debouncer.enqueue(); + } +} + +const projectBuilder = new ProjectQueue((projects, lkg, force) => exec(process.execPath, [lkg ? "./lib/tsc" : "./built/local/tsc", "-b", ...(force ? ["--force"] : []), ...projects], { hidePrompt: true })); + +/** + * @param {string} project + * @param {object} [options] + * @param {boolean} [options.lkg=true] + * @param {boolean} [options.force=false] + */ +exports.buildProject = (project, { lkg, force } = {}) => projectBuilder.enqueue(project, { lkg, force }); + +const projectCleaner = new ProjectQueue((projects, lkg) => exec(process.execPath, [lkg ? "./lib/tsc" : "./built/local/tsc", "-b", "--clean", ...projects], { hidePrompt: true })); + +/** + * @param {string} project + */ +exports.cleanProject = (project) => projectCleaner.enqueue(project); + +const projectWatcher = new ProjectQueue((projects) => exec(process.execPath, ["./lib/tsc", "-b", "--watch", ...projects], { hidePrompt: true })); + +/** + * @param {string} project + * @param {object} [options] + * @param {boolean} [options.lkg=true] + */ +exports.watchProject = (project, { lkg } = {}) => projectWatcher.enqueue(project, { lkg }); diff --git a/scripts/build/readJson.js b/scripts/build/readJson.js deleted file mode 100644 index c9e63419692..00000000000 --- a/scripts/build/readJson.js +++ /dev/null @@ -1,17 +0,0 @@ -// @ts-check -const ts = require("../../lib/typescript"); -const fs = require("fs"); -const { reportDiagnostics } = require("./diagnostics"); - -module.exports = exports = readJson; - -/** @param {string} jsonPath */ -function readJson(jsonPath) { - const jsonText = fs.readFileSync(jsonPath, "utf8"); - const result = ts.parseConfigFileTextToJson(jsonPath, jsonText); - if (result.error) { - reportDiagnostics([result.error]); - throw new Error("An error occurred during parse."); - } - return result.config; -} \ No newline at end of file diff --git a/scripts/build/replace.js b/scripts/build/replace.js deleted file mode 100644 index 1287d082287..00000000000 --- a/scripts/build/replace.js +++ /dev/null @@ -1,12 +0,0 @@ -// @ts-check -const insert = require("gulp-insert"); - -/** - * @param {string | RegExp} searchValue - * @param {string | ((...args: string[]) => string)} replacer - */ -function replace(searchValue, replacer) { - return insert.transform(content => content.replace(searchValue, /**@type {string}*/(replacer))); -} - -module.exports = replace; \ No newline at end of file diff --git a/scripts/build/rm.js b/scripts/build/rm.js deleted file mode 100644 index 0922954b49d..00000000000 --- a/scripts/build/rm.js +++ /dev/null @@ -1,84 +0,0 @@ -// @ts-check -const { Duplex } = require("stream"); -const path = require("path"); -const Vinyl = require("vinyl"); -const del = require("del"); - -module.exports = rm; - -/** - * @param {string | ((file: File) => string) | Options} [dest] - * @param {Options} [opts] - */ -function rm(dest, opts) { - if (dest && typeof dest === "object") opts = dest, dest = undefined; - let failed = false; - - const cwd = path.resolve(opts && opts.cwd || process.cwd()); - - /** @type {{ file: File, deleted: boolean, promise: Promise, cb: Function }[]} */ - const pending = []; - - const processDeleted = () => { - if (failed) return; - while (pending.length && pending[0].deleted) { - const { file, cb } = pending.shift(); - duplex.push(file); - cb(); - } - }; - - const duplex = new Duplex({ - objectMode: true, - /** - * @param {string|Buffer|File} file - */ - write(file, _, cb) { - if (failed) return; - if (typeof file === "string" || Buffer.isBuffer(file)) return cb(new Error("Only Vinyl files are supported.")); - const basePath = typeof dest === "string" ? path.resolve(cwd, dest) : - typeof dest === "function" ? path.resolve(cwd, dest(file)) : - file.base; - const filePath = path.resolve(basePath, file.relative); - file.cwd = cwd; - file.base = basePath; - file.path = filePath; - const entry = { - file, - deleted: false, - cb, - promise: del(file.path).then(() => { - entry.deleted = true; - processDeleted(); - }, err => { - failed = true; - pending.length = 0; - cb(err); - }) - }; - pending.push(entry); - }, - final(cb) { - processDeleted(); - if (pending.length) { - Promise - .all(pending.map(entry => entry.promise)) - .then(() => processDeleted()) - .then(() => cb(), cb); - return; - } - cb(); - }, - read() { - } - }); - return duplex; -} - -/** - * @typedef {import("vinyl")} File - * - * @typedef Options - * @property {string} [cwd] - */ -void 0; \ No newline at end of file diff --git a/scripts/build/sourcemaps.js b/scripts/build/sourcemaps.js index 66e488287b2..2c85b897b09 100644 --- a/scripts/build/sourcemaps.js +++ b/scripts/build/sourcemaps.js @@ -1,12 +1,13 @@ // @ts-check +/// + const path = require("path"); -const Vinyl = require("./vinyl"); const convertMap = require("convert-source-map"); const applySourceMap = require("vinyl-sourcemaps-apply"); const through2 = require("through2"); /** - * @param {Vinyl} input + * @param {import("vinyl")} input * @param {string | Buffer} contents * @param {string | RawSourceMap} [sourceMap] */ @@ -16,13 +17,13 @@ function replaceContents(input, contents, sourceMap) { if (input.sourceMap) { output.sourceMap = typeof input.sourceMap === "string" ? /**@type {RawSourceMap}*/(JSON.parse(input.sourceMap)) : input.sourceMap; if (typeof sourceMap === "string") { - sourceMap = /**@type {RawSourceMap}*/(JSON.parse(sourceMap)); + sourceMap = /** @type {RawSourceMap} */(JSON.parse(sourceMap)); } else if (sourceMap === undefined) { const stringContents = typeof contents === "string" ? contents : contents.toString("utf8"); const newSourceMapConverter = convertMap.fromSource(stringContents); if (newSourceMapConverter) { - sourceMap = /**@type {RawSourceMap}*/(newSourceMapConverter.toObject()); + sourceMap = /** @type {RawSourceMap} */(newSourceMapConverter.toObject()); output.contents = new Buffer(convertMap.removeMapFileComments(stringContents), "utf8"); } } @@ -31,7 +32,7 @@ function replaceContents(input, contents, sourceMap) { const base = input.base || cwd; const sourceRoot = output.sourceMap.sourceRoot; makeAbsoluteSourceMap(cwd, base, output.sourceMap); - makeAbsoluteSourceMap(cwd, base, sourceMap); + makeAbsoluteSourceMap(cwd, base, /** @type {RawSourceMap} */(sourceMap)); applySourceMap(output, sourceMap); makeRelativeSourceMap(cwd, base, sourceRoot, output.sourceMap); } @@ -44,10 +45,12 @@ function replaceContents(input, contents, sourceMap) { exports.replaceContents = replaceContents; function removeSourceMaps() { - return through2.obj((/**@type {Vinyl}*/file, _, cb) => { - if (file.sourceMap && file.isBuffer()) { + return through2.obj((/**@type {import("vinyl")}*/file, _, cb) => { + if (file.isBuffer()) { file.contents = Buffer.from(convertMap.removeMapFileComments(file.contents.toString("utf8")), "utf8"); - file.sourceMap = undefined; + if (file.sourceMap) { + file.sourceMap = undefined; + } } cb(null, file); }); @@ -59,7 +62,7 @@ exports.removeSourceMaps = removeSourceMaps; * @param {string | undefined} base * @param {RawSourceMap} sourceMap * - * @typedef RawSourceMap + * @typedef {object} RawSourceMap * @property {string} version * @property {string} file * @property {string} [sourceRoot] diff --git a/scripts/build/tests.js b/scripts/build/tests.js index 5bc619e3823..36a9ea54cb9 100644 --- a/scripts/build/tests.js +++ b/scripts/build/tests.js @@ -1,16 +1,16 @@ // @ts-check -const gulp = require("./gulp"); +const gulp = require("gulp"); const del = require("del"); const fs = require("fs"); const os = require("os"); const path = require("path"); -const mkdirP = require("./mkdirp"); +const mkdirP = require("mkdirp"); +const log = require("fancy-log"); const cmdLineOptions = require("./options"); -const exec = require("./exec"); -const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util) const { CancellationToken } = require("prex"); -const mochaJs = require.resolve("mocha/bin/_mocha"); +const { exec } = require("./utils"); +const mochaJs = require.resolve("mocha/bin/_mocha"); exports.localBaseline = "tests/baselines/local/"; exports.refBaseline = "tests/baselines/reference/"; exports.localRwcBaseline = "internal/baselines/rwc/local"; @@ -27,7 +27,6 @@ exports.localTest262Baseline = "internal/baselines/test262/local"; async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, cancelToken = CancellationToken.none) { let testTimeout = cmdLineOptions.timeout; let tests = cmdLineOptions.tests; - const lintFlag = cmdLineOptions.lint; const debug = cmdLineOptions.debug; const inspect = cmdLineOptions.inspect; const runners = cmdLineOptions.runners; @@ -117,9 +116,6 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, errorStatus = exitCode; error = new Error(`Process exited with status code ${errorStatus}.`); } - else if (lintFlag) { - await new Promise((resolve, reject) => gulp.start(["lint"], error => error ? reject(error) : resolve())); - } } catch (e) { errorStatus = undefined; @@ -144,10 +140,10 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, } exports.runConsoleTests = runConsoleTests; -function cleanTestDirs() { - return del([exports.localBaseline, exports.localRwcBaseline]) - .then(() => mkdirP(exports.localRwcBaseline)) - .then(() => mkdirP(exports.localBaseline)); +async function cleanTestDirs() { + await del([exports.localBaseline, exports.localRwcBaseline]) + mkdirP.sync(exports.localRwcBaseline); + mkdirP.sync(exports.localBaseline); } exports.cleanTestDirs = cleanTestDirs; @@ -165,7 +161,7 @@ exports.cleanTestDirs = cleanTestDirs; function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, timeout, keepFailed) { const testConfigContents = JSON.stringify({ test: tests ? [tests] : undefined, - runner: runners ? runners.split(",") : undefined, + runners: runners ? runners.split(",") : undefined, light, workerCount, stackTraceLimit, @@ -192,4 +188,4 @@ function restoreSavedNodeEnv() { function deleteTemporaryProjectOutput() { return del(path.join(exports.localBaseline, "projectOutput/")); -} \ No newline at end of file +} diff --git a/scripts/build/upToDate.js b/scripts/build/upToDate.js deleted file mode 100644 index c3abb5d1013..00000000000 --- a/scripts/build/upToDate.js +++ /dev/null @@ -1,435 +0,0 @@ -// @ts-check -const path = require("path"); -const fs = require("fs"); -const log = require("fancy-log"); // was `require("gulp-util").log (see https://github.com/gulpjs/gulp-util) -const ts = require("../../lib/typescript"); -const { Duplex } = require("stream"); -const chalk = /**@type {*} */(require("chalk")); -const Vinyl = require("vinyl"); - -/** - * Creates a stream that passes through its inputs only if the project outputs are not up to date - * with respect to the inputs. - * @param {ParsedCommandLine} parsedProject - * @param {UpToDateOptions} [options] - * - * @typedef UpToDateOptions - * @property {boolean | "minimal"} [verbose] - * @property {(configFilePath: string) => ParsedCommandLine | undefined} [parseProject] - */ -function upToDate(parsedProject, options) { - /** @type {File[]} */ - const inputs = []; - /** @type {Map} */ - const inputMap = new Map(); - /** @type {Map} */ - const statCache = new Map(); - /** @type {UpToDateHost} */ - const upToDateHost = { - fileExists(fileName) { - const stats = getStat(fileName); - return stats ? stats.isFile() : false; - }, - getModifiedTime(fileName) { - return getStat(fileName).mtime; - }, - parseConfigFile: options && options.parseProject - }; - const duplex = new Duplex({ - objectMode: true, - /** - * @param {string|Buffer|File} file - */ - write(file, _, cb) { - if (typeof file === "string" || Buffer.isBuffer(file)) return cb(new Error("Only Vinyl files are supported.")); - inputs.push(file); - inputMap.set(path.resolve(file.path), file); - cb(); - }, - final(cb) { - const status = getUpToDateStatus(upToDateHost, parsedProject); - reportStatus(parsedProject, status, options); - if (status.type !== UpToDateStatusType.UpToDate) { - for (const input of inputs) duplex.push(input); - } - duplex.push(null); - inputMap.clear(); - statCache.clear(); - cb(); - }, - read() { - } - }); - return duplex; - - function getStat(fileName) { - fileName = path.resolve(fileName); - const inputFile = inputMap.get(fileName); - if (inputFile && inputFile.stat) return inputFile.stat; - - let stats = statCache.get(fileName); - if (!stats && fs.existsSync(fileName)) { - stats = fs.statSync(fileName); - statCache.set(fileName, stats); - } - return stats; - } -} -module.exports = exports = upToDate; - -/** - * @param {DiagnosticMessage} message - * @param {...string} args - */ -function formatMessage(message, ...args) { - log.info(formatStringFromArgs(message.message, args)); -} - -/** - * @param {ParsedCommandLine} project - * @param {UpToDateStatus} status - * @param {{verbose?: boolean | "minimal"}} options - */ -function reportStatus(project, status, options) { - switch (options.verbose) { - case "minimal": - switch (status.type) { - case UpToDateStatusType.UpToDate: - log.info(`Project '${fileName(project.options.configFilePath)}' is up to date.`); - break; - default: - log.info(`Project '${fileName(project.options.configFilePath)}' is out of date, rebuilding...`); - break; - } - break; - case true: - /**@type {*}*/(ts).formatUpToDateStatus(project.options.configFilePath, status, fileName, formatMessage); - break; - } - if (!options.verbose) return; -} - -/** - * @param {string} file - * @private - */ -function normalizeSlashes(file) { - return file.replace(/\\/g, "/"); -} - -/** - * @param {string} file - * @private - */ -function fileName(file) { - return chalk.cyan(normalizeSlashes(path.relative(process.cwd(), path.resolve(file)))); -} - -/** - * @param {string} text - * @param {string[]} args - * @param {number} [baseIndex] - */ -function formatStringFromArgs(text, args, baseIndex = 0) { - return text.replace(/{(\d+)}/g, (_match, index) => args[+index + baseIndex]); -} - -const minimumDate = new Date(-8640000000000000); -const maximumDate = new Date(8640000000000000); -const missingFileModifiedTime = new Date(0); - -/** - * @typedef {0} UpToDateStatusType.Unbuildable - * @typedef {1} UpToDateStatusType.UpToDate - * @typedef {2} UpToDateStatusType.UpToDateWithUpstreamTypes - * @typedef {3} UpToDateStatusType.OutputMissing - * @typedef {4} UpToDateStatusType.OutOfDateWithSelf - * @typedef {5} UpToDateStatusType.OutOfDateWithUpstream - * @typedef {6} UpToDateStatusType.UpstreamOutOfDate - * @typedef {7} UpToDateStatusType.UpstreamBlocked - * @typedef {8} UpToDateStatusType.ComputingUpstream - * @typedef {9} UpToDateStatusType.ContainerOnly - * @enum {UpToDateStatusType.Unbuildable | UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes | UpToDateStatusType.OutputMissing | UpToDateStatusType.OutOfDateWithSelf | UpToDateStatusType.OutOfDateWithUpstream | UpToDateStatusType.UpstreamOutOfDate | UpToDateStatusType.UpstreamBlocked | UpToDateStatusType.ComputingUpstream | UpToDateStatusType.ContainerOnly} - */ -const UpToDateStatusType = { - Unbuildable: /** @type {0} */(0), - UpToDate: /** @type {1} */(1), - UpToDateWithUpstreamTypes: /** @type {2} */(2), - OutputMissing: /** @type {3} */(3), - OutOfDateWithSelf: /** @type {4} */(4), - OutOfDateWithUpstream: /** @type {5} */(5), - UpstreamOutOfDate: /** @type {6} */(6), - UpstreamBlocked: /** @type {7} */(7), - ComputingUpstream: /** @type {8} */(8), - ContainerOnly: /** @type {9} */(9), -}; - -/** - * @param {Date} date1 - * @param {Date} date2 - * @returns {Date} - */ -function newer(date1, date2) { - return date2 > date1 ? date2 : date1; -} - -/** - * @param {UpToDateHost} host - * @param {ParsedCommandLine | undefined} project - * @returns {UpToDateStatus} - */ -function getUpToDateStatus(host, project) { - if (project === undefined) return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; - const prior = host.getLastStatus ? host.getLastStatus(project.options.configFilePath) : undefined; - if (prior !== undefined) { - return prior; - } - const actual = getUpToDateStatusWorker(host, project); - if (host.setLastStatus) { - host.setLastStatus(project.options.configFilePath, actual); - } - return actual; -} - -/** - * @param {UpToDateHost} host - * @param {ParsedCommandLine | undefined} project - * @returns {UpToDateStatus} - */ -function getUpToDateStatusWorker(host, project) { - /** @type {string} */ - let newestInputFileName = undefined; - let newestInputFileTime = minimumDate; - // Get timestamps of input files - for (const inputFile of project.fileNames) { - if (!host.fileExists(inputFile)) { - return { - type: UpToDateStatusType.Unbuildable, - reason: `${inputFile} does not exist` - }; - } - - const inputTime = host.getModifiedTime(inputFile) || missingFileModifiedTime; - if (inputTime > newestInputFileTime) { - newestInputFileName = inputFile; - newestInputFileTime = inputTime; - } - } - - // Collect the expected outputs of this project - const outputs = /**@type {string[]}*/(/**@type {*}*/(ts).getAllProjectOutputs(project)); - - if (outputs.length === 0) { - return { - type: UpToDateStatusType.ContainerOnly - }; - } - - // Now see if all outputs are newer than the newest input - let oldestOutputFileName = "(none)"; - let oldestOutputFileTime = maximumDate; - let newestOutputFileName = "(none)"; - let newestOutputFileTime = minimumDate; - /** @type {string | undefined} */ - let missingOutputFileName; - let newestDeclarationFileContentChangedTime = minimumDate; - let isOutOfDateWithInputs = false; - for (const output of outputs) { - // Output is missing; can stop checking - // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status - if (!host.fileExists(output)) { - missingOutputFileName = output; - break; - } - - const outputTime = host.getModifiedTime(output) || missingFileModifiedTime; - if (outputTime < oldestOutputFileTime) { - oldestOutputFileTime = outputTime; - oldestOutputFileName = output; - } - - // If an output is older than the newest input, we can stop checking - // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status - if (outputTime < newestInputFileTime) { - isOutOfDateWithInputs = true; - break; - } - - if (outputTime > newestOutputFileTime) { - newestOutputFileTime = outputTime; - newestOutputFileName = output; - } - - // Keep track of when the most recent time a .d.ts file was changed. - // In addition to file timestamps, we also keep track of when a .d.ts file - // had its file touched but not had its contents changed - this allows us - // to skip a downstream typecheck - if (path.extname(output) === ".d.ts") { - const unchangedTime = host.getUnchangedTime ? host.getUnchangedTime(output) : undefined; - if (unchangedTime !== undefined) { - newestDeclarationFileContentChangedTime = newer(unchangedTime, newestDeclarationFileContentChangedTime); - } - else { - const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime; - newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime); - } - } - } - - let pseudoUpToDate = false; - let usesPrepend = false; - /** @type {string | undefined} */ - let upstreamChangedProject; - if (project.projectReferences) { - if (host.setLastStatus) host.setLastStatus(project.options.configFilePath, { type: UpToDateStatusType.ComputingUpstream }); - for (const ref of project.projectReferences) { - usesPrepend = usesPrepend || !!(ref.prepend); - const resolvedRef = ts.resolveProjectReferencePath(host, ref); - const parsedRef = host.parseConfigFile ? host.parseConfigFile(resolvedRef) : ts.getParsedCommandLineOfConfigFile(resolvedRef, {}, parseConfigHost); - const refStatus = getUpToDateStatus(host, parsedRef); - - // Its a circular reference ignore the status of this project - if (refStatus.type === UpToDateStatusType.ComputingUpstream) { - continue; - } - - // An upstream project is blocked - if (refStatus.type === UpToDateStatusType.Unbuildable) { - return { - type: UpToDateStatusType.UpstreamBlocked, - upstreamProjectName: ref.path - }; - } - - // If the upstream project is out of date, then so are we (someone shouldn't have asked, though?) - if (refStatus.type !== UpToDateStatusType.UpToDate) { - return { - type: UpToDateStatusType.UpstreamOutOfDate, - upstreamProjectName: ref.path - }; - } - - // If the upstream project's newest file is older than our oldest output, we - // can't be out of date because of it - if (refStatus.newestInputFileTime && refStatus.newestInputFileTime <= oldestOutputFileTime) { - continue; - } - - // If the upstream project has only change .d.ts files, and we've built - // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild - if (refStatus.newestDeclarationFileContentChangedTime && refStatus.newestDeclarationFileContentChangedTime <= oldestOutputFileTime) { - pseudoUpToDate = true; - upstreamChangedProject = ref.path; - continue; - } - - // We have an output older than an upstream output - we are out of date - return { - type: UpToDateStatusType.OutOfDateWithUpstream, - outOfDateOutputFileName: oldestOutputFileName, - newerProjectName: ref.path - }; - } - } - - if (missingOutputFileName !== undefined) { - return { - type: UpToDateStatusType.OutputMissing, - missingOutputFileName - }; - } - - if (isOutOfDateWithInputs) { - return { - type: UpToDateStatusType.OutOfDateWithSelf, - outOfDateOutputFileName: oldestOutputFileName, - newerInputFileName: newestInputFileName - }; - } - - if (usesPrepend && pseudoUpToDate) { - return { - type: UpToDateStatusType.OutOfDateWithUpstream, - outOfDateOutputFileName: oldestOutputFileName, - newerProjectName: upstreamChangedProject - }; - } - - // Up to date - return { - type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime, - newestInputFileTime, - newestOutputFileTime, - newestInputFileName, - newestOutputFileName, - oldestOutputFileName - }; -} - -const parseConfigHost = { - useCaseSensitiveFileNames: true, - getCurrentDirectory: () => process.cwd(), - readDirectory: (file) => fs.readdirSync(file), - fileExists: file => fs.existsSync(file) && fs.statSync(file).isFile(), - readFile: file => fs.readFileSync(file, "utf8"), - onUnRecoverableConfigFileDiagnostic: () => undefined -}; - -/** - * @typedef {import("vinyl")} File - * @typedef {import("../../lib/typescript").ParsedCommandLine & { options: CompilerOptions }} ParsedCommandLine - * @typedef {import("../../lib/typescript").CompilerOptions & { configFilePath?: string }} CompilerOptions - * @typedef {import("../../lib/typescript").DiagnosticMessage} DiagnosticMessage - * @typedef UpToDateHost - * @property {(fileName: string) => boolean} fileExists - * @property {(fileName: string) => Date} getModifiedTime - * @property {(fileName: string) => Date} [getUnchangedTime] - * @property {(configFilePath: string) => ParsedCommandLine | undefined} parseConfigFile - * @property {(configFilePath: string) => UpToDateStatus} [getLastStatus] - * @property {(configFilePath: string, status: UpToDateStatus) => void} [setLastStatus] - * - * @typedef Status.Unbuildable - * @property {UpToDateStatusType.Unbuildable} type - * @property {string} reason - * - * @typedef Status.ContainerOnly - * @property {UpToDateStatusType.ContainerOnly} type - * - * @typedef Status.UpToDate - * @property {UpToDateStatusType.UpToDate | UpToDateStatusType.UpToDateWithUpstreamTypes} type - * @property {Date} [newestInputFileTime] - * @property {string} [newestInputFileName] - * @property {Date} [newestDeclarationFileContentChangedTime] - * @property {Date} [newestOutputFileTime] - * @property {string} [newestOutputFileName] - * @property {string} [oldestOutputFileName] - * - * @typedef Status.OutputMissing - * @property {UpToDateStatusType.OutputMissing} type - * @property {string} missingOutputFileName - * - * @typedef Status.OutOfDateWithSelf - * @property {UpToDateStatusType.OutOfDateWithSelf} type - * @property {string} outOfDateOutputFileName - * @property {string} newerInputFileName - * - * @typedef Status.UpstreamOutOfDate - * @property {UpToDateStatusType.UpstreamOutOfDate} type - * @property {string} upstreamProjectName - * - * @typedef Status.UpstreamBlocked - * @property {UpToDateStatusType.UpstreamBlocked} type - * @property {string} upstreamProjectName - * - * @typedef Status.ComputingUpstream - * @property {UpToDateStatusType.ComputingUpstream} type - * - * @typedef Status.OutOfDateWithUpstream - * @property {UpToDateStatusType.OutOfDateWithUpstream} type - * @property {string} outOfDateOutputFileName - * @property {string} newerProjectName - - * @typedef {Status.Unbuildable | Status.ContainerOnly | Status.UpToDate | Status.OutputMissing | Status.OutOfDateWithSelf | Status.UpstreamOutOfDate | Status.UpstreamBlocked | Status.ComputingUpstream | Status.OutOfDateWithUpstream} UpToDateStatus - */ -void 0; \ No newline at end of file diff --git a/scripts/build/utils.js b/scripts/build/utils.js index 06f55d7288a..170c36adef6 100644 --- a/scripts/build/utils.js +++ b/scripts/build/utils.js @@ -1,7 +1,119 @@ // @ts-check +/// + const fs = require("fs"); -const File = require("./vinyl"); -const { Readable } = require("stream"); +const path = require("path"); +const log = require("fancy-log"); +const mkdirp = require("mkdirp"); +const del = require("del"); +const File = require("vinyl"); +const ts = require("../../lib/typescript"); +const { default: chalk } = require("chalk"); +const { spawn } = require("child_process"); +const { CancellationToken, CancelError, Deferred } = require("prex"); +const { Readable, Duplex } = require("stream"); + +const isWindows = /^win/.test(process.platform); + +/** + * Executes the provided command once with the supplied arguments. + * @param {string} cmd + * @param {string[]} args + * @param {ExecOptions} [options] + * + * @typedef ExecOptions + * @property {boolean} [ignoreExitCode] + * @property {import("prex").CancellationToken} [cancelToken] + * @property {boolean} [hidePrompt] + */ +function exec(cmd, args, options = {}) { + return /**@type {Promise<{exitCode: number}>}*/(new Promise((resolve, reject) => { + const { ignoreExitCode, cancelToken = CancellationToken.none } = options; + cancelToken.throwIfCancellationRequested(); + + // TODO (weswig): Update child_process types to add windowsVerbatimArguments to the type definition + const subshellFlag = isWindows ? "/c" : "-c"; + const command = isWindows ? [possiblyQuote(cmd), ...args] : [`${cmd} ${args.join(" ")}`]; + + if (!options.hidePrompt) log(`> ${chalk.green(cmd)} ${args.join(" ")}`); + const proc = spawn(isWindows ? "cmd" : "/bin/sh", [subshellFlag, ...command], { stdio: "inherit", windowsVerbatimArguments: true }); + const registration = cancelToken.register(() => { + log(`${chalk.red("killing")} '${chalk.green(cmd)} ${args.join(" ")}'...`); + proc.kill("SIGINT"); + proc.kill("SIGTERM"); + reject(new CancelError()); + }); + proc.on("exit", exitCode => { + registration.unregister(); + if (exitCode === 0 || ignoreExitCode) { + resolve({ exitCode }); + } + else { + reject(new Error(`Process exited with code: ${exitCode}`)); + } + }); + proc.on("error", error => { + registration.unregister(); + reject(error); + }); + })); +} +exports.exec = exec; + +/** + * @param {string} cmd + */ +function possiblyQuote(cmd) { + return cmd.indexOf(" ") >= 0 ? `"${cmd}"` : cmd; +} + +/** + * @param {ts.Diagnostic[]} diagnostics + * @param {{ cwd?: string, pretty?: boolean }} [options] + */ +function formatDiagnostics(diagnostics, options) { + return options && options.pretty + ? ts.formatDiagnosticsWithColorAndContext(diagnostics, getFormatDiagnosticsHost(options && options.cwd)) + : ts.formatDiagnostics(diagnostics, getFormatDiagnosticsHost(options && options.cwd)); +} +exports.formatDiagnostics = formatDiagnostics; + +/** + * @param {ts.Diagnostic[]} diagnostics + * @param {{ cwd?: string }} [options] + */ +function reportDiagnostics(diagnostics, options) { + log(formatDiagnostics(diagnostics, { cwd: options && options.cwd, pretty: process.stdout.isTTY })); +} +exports.reportDiagnostics = reportDiagnostics; + +/** + * @param {string | undefined} cwd + * @returns {ts.FormatDiagnosticsHost} + */ +function getFormatDiagnosticsHost(cwd) { + return { + getCanonicalFileName: fileName => fileName, + getCurrentDirectory: () => cwd, + getNewLine: () => ts.sys.newLine, + }; +} +exports.getFormatDiagnosticsHost = getFormatDiagnosticsHost; + +/** + * Reads JSON data with optional comments using the LKG TypeScript compiler + * @param {string} jsonPath + */ +function readJson(jsonPath) { + const jsonText = fs.readFileSync(jsonPath, "utf8"); + const result = ts.parseConfigFileTextToJson(jsonPath, jsonText); + if (result.error) { + reportDiagnostics([result.error]); + throw new Error("An error occurred during parse."); + } + return result.config; +} +exports.readJson = readJson; /** * @param {File} file @@ -24,4 +136,299 @@ function streamFromBuffer(buffer) { } }); } -exports.streamFromBuffer = streamFromBuffer; \ No newline at end of file +exports.streamFromBuffer = streamFromBuffer; + +/** + * @param {string | string[]} source + * @param {string | string[]} dest + * @returns {boolean} + */ +function needsUpdate(source, dest) { + if (typeof source === "string" && typeof dest === "string") { + if (fs.existsSync(dest)) { + const {mtime: outTime} = fs.statSync(dest); + const {mtime: inTime} = fs.statSync(source); + if (+inTime <= +outTime) { + return false; + } + } + } + else if (typeof source === "string" && typeof dest !== "string") { + const {mtime: inTime} = fs.statSync(source); + for (const filepath of dest) { + if (fs.existsSync(filepath)) { + const {mtime: outTime} = fs.statSync(filepath); + if (+inTime > +outTime) { + return true; + } + } + else { + return true; + } + } + return false; + } + else if (typeof source !== "string" && typeof dest === "string") { + if (fs.existsSync(dest)) { + const {mtime: outTime} = fs.statSync(dest); + for (const filepath of source) { + if (fs.existsSync(filepath)) { + const {mtime: inTime} = fs.statSync(filepath); + if (+inTime > +outTime) { + return true; + } + } + else { + return true; + } + } + return false; + } + } + else if (typeof source !== "string" && typeof dest !== "string") { + for (let i = 0; i < source.length; i++) { + if (!dest[i]) { + continue; + } + if (fs.existsSync(dest[i])) { + const {mtime: outTime} = fs.statSync(dest[i]); + const {mtime: inTime} = fs.statSync(source[i]); + if (+inTime > +outTime) { + return true; + } + } + else { + return true; + } + } + return false; + } + return true; +} +exports.needsUpdate = needsUpdate; + +function getDiffTool() { + const program = process.env.DIFF; + if (!program) { + log.warn("Add the 'DIFF' environment variable to the path of the program you want to use."); + process.exit(1); + } + return program; +} +exports.getDiffTool = getDiffTool; + +/** + * Find the size of a directory recursively. + * Symbolic links can cause a loop. + * @param {string} root + * @returns {number} bytes + */ +function getDirSize(root) { + const stats = fs.lstatSync(root); + + if (!stats.isDirectory()) { + return stats.size; + } + + return fs.readdirSync(root) + .map(file => getDirSize(path.join(root, file))) + .reduce((acc, num) => acc + num, 0); +} +exports.getDirSize = getDirSize; + +/** + * Flattens a project with project references into a single project. + * @param {string} projectSpec The path to a tsconfig.json file or its containing directory. + * @param {string} flattenedProjectSpec The output path for the flattened tsconfig.json file. + * @param {FlattenOptions} [options] Options used to flatten a project hierarchy. + * + * @typedef FlattenOptions + * @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`. + * @property {import("../../lib/typescript").CompilerOptions} [compilerOptions] Compiler option overrides. + * @property {boolean} [force] Forces creation of the output project. + * @property {string[]} [exclude] Files to exclude (relative to `cwd`) + */ +function flatten(projectSpec, flattenedProjectSpec, options = {}) { + const cwd = normalizeSlashes(options.cwd ? path.resolve(options.cwd) : process.cwd()); + const files = []; + const resolvedOutputSpec = path.resolve(cwd, flattenedProjectSpec); + const resolvedOutputDirectory = path.dirname(resolvedOutputSpec); + const resolvedProjectSpec = resolveProjectSpec(projectSpec, cwd, undefined); + const project = readJson(resolvedProjectSpec); + const skipProjects = /**@type {Set}*/(new Set()); + const skipFiles = new Set(options && options.exclude && options.exclude.map(file => normalizeSlashes(path.resolve(cwd, file)))); + recur(resolvedProjectSpec, project); + + if (options.force || needsUpdate(files, resolvedOutputSpec)) { + const config = { + extends: normalizeSlashes(path.relative(resolvedOutputDirectory, resolvedProjectSpec)), + compilerOptions: options.compilerOptions || {}, + files: files.map(file => normalizeSlashes(path.relative(resolvedOutputDirectory, file))) + }; + mkdirp.sync(resolvedOutputDirectory); + fs.writeFileSync(resolvedOutputSpec, JSON.stringify(config, undefined, 2), "utf8"); + } + + /** + * @param {string} projectSpec + * @param {object} project + */ + function recur(projectSpec, project) { + if (skipProjects.has(projectSpec)) return; + skipProjects.add(project); + if (project.references) { + for (const ref of project.references) { + const referencedSpec = resolveProjectSpec(ref.path, cwd, projectSpec); + const referencedProject = readJson(referencedSpec); + recur(referencedSpec, referencedProject); + } + } + if (project.include) { + throw new Error("Flattened project may not have an 'include' list."); + } + if (!project.files) { + throw new Error("Flattened project must have an explicit 'files' list."); + } + const projectDirectory = path.dirname(projectSpec); + for (let file of project.files) { + file = normalizeSlashes(path.resolve(projectDirectory, file)); + if (skipFiles.has(file)) continue; + skipFiles.add(file); + files.push(file); + } + } +} +exports.flatten = flatten; + +/** + * @param {string} file + */ +function normalizeSlashes(file) { + return file.replace(/\\/g, "/"); +} + +/** + * @param {string} projectSpec + * @param {string} cwd + * @param {string | undefined} referrer + * @returns {string} + */ +function resolveProjectSpec(projectSpec, cwd, referrer) { + let projectPath = normalizeSlashes(path.resolve(cwd, referrer ? path.dirname(referrer) : "", projectSpec)); + const stats = fs.statSync(projectPath); + if (stats.isFile()) return normalizeSlashes(projectPath); + return normalizeSlashes(path.resolve(cwd, projectPath, "tsconfig.json")); +} + +/** + * @param {string | ((file: File) => string) | { cwd?: string }} [dest] + * @param {{ cwd?: string }} [opts] + */ +function rm(dest, opts) { + if (dest && typeof dest === "object") opts = dest, dest = undefined; + let failed = false; + + const cwd = path.resolve(opts && opts.cwd || process.cwd()); + + /** @type {{ file: File, deleted: boolean, promise: Promise, cb: Function }[]} */ + const pending = []; + + const processDeleted = () => { + if (failed) return; + while (pending.length && pending[0].deleted) { + const { file, cb } = pending.shift(); + duplex.push(file); + cb(); + } + }; + + const duplex = new Duplex({ + objectMode: true, + /** + * @param {string|Buffer|File} file + */ + write(file, _, cb) { + if (failed) return; + if (typeof file === "string" || Buffer.isBuffer(file)) return cb(new Error("Only Vinyl files are supported.")); + const basePath = typeof dest === "string" ? path.resolve(cwd, dest) : + typeof dest === "function" ? path.resolve(cwd, dest(file)) : + file.base; + const filePath = path.resolve(basePath, file.relative); + file.cwd = cwd; + file.base = basePath; + file.path = filePath; + const entry = { + file, + deleted: false, + cb, + promise: del(file.path).then(() => { + entry.deleted = true; + processDeleted(); + }, err => { + failed = true; + pending.length = 0; + cb(err); + }) + }; + pending.push(entry); + }, + final(cb) { + processDeleted(); + if (pending.length) { + Promise + .all(pending.map(entry => entry.promise)) + .then(() => processDeleted()) + .then(() => cb(), cb); + return; + } + cb(); + }, + read() { + } + }); + return duplex; +} +exports.rm = rm; + +class Debouncer { + /** + * @param {number} timeout + * @param {() => Promise} action + */ + constructor(timeout, action) { + this._timeout = timeout; + this._action = action; + } + + enqueue() { + if (this._timer) { + clearTimeout(this._timer); + this._timer = undefined; + } + + if (!this._deferred) { + this._deferred = new Deferred(); + } + + this._timer = setTimeout(() => this.run(), 100); + return this._deferred.promise; + } + + run() { + if (this._timer) { + clearTimeout(this._timer); + this._timer = undefined; + } + + const deferred = this._deferred; + this._deferred = undefined; + this._projects = undefined; + try { + deferred.resolve(this._action()); + } + catch (e) { + deferred.reject(e); + } + } +} +exports.Debouncer = Debouncer; \ No newline at end of file diff --git a/scripts/build/vinyl.d.ts b/scripts/build/vinyl.d.ts deleted file mode 100644 index 1dfb6314991..00000000000 --- a/scripts/build/vinyl.d.ts +++ /dev/null @@ -1,60 +0,0 @@ -// NOTE: This makes it possible to correctly type vinyl Files under @ts-check. -export = File; - -declare class File { - constructor(options?: File.VinylOptions); - - cwd: string; - base: string; - path: string; - readonly history: ReadonlyArray; - contents: T; - relative: string; - dirname: string; - basename: string; - stem: string; - extname: string; - symlink: string | null; - stat: import("fs").Stats | null; - sourceMap?: import("./sourcemaps").RawSourceMap | string; - - [custom: string]: any; - - isBuffer(): this is T extends Buffer ? File : never; - isStream(): this is T extends NodeJS.ReadableStream ? File : never; - isNull(): this is T extends null ? File : never; - isDirectory(): this is T extends null ? File.Directory : never; - isSymbolic(): this is T extends null ? File.Symbolic : never; - clone(opts?: { contents?: boolean, deep?: boolean }): this; -} - -namespace File { - export interface VinylOptions { - cwd?: string; - base?: string; - path?: string; - history?: ReadonlyArray; - stat?: import("fs").Stats; - contents?: T; - sourceMap?: import("./sourcemaps").RawSourceMap | string; - [custom: string]: any; - } - - export type Contents = Buffer | NodeJS.ReadableStream | null; - export type File = import("./vinyl"); - export type NullFile = File; - export type BufferFile = File; - export type StreamFile = File; - - export interface Directory extends NullFile { - isNull(): true; - isDirectory(): true; - isSymbolic(): this is never; - } - - export interface Symbolic extends NullFile { - isNull(): true; - isDirectory(): this is never; - isSymbolic(): true; - } -} \ No newline at end of file diff --git a/scripts/build/vinyl.js b/scripts/build/vinyl.js deleted file mode 100644 index 6cf68f3cd26..00000000000 --- a/scripts/build/vinyl.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("vinyl"); \ No newline at end of file diff --git a/scripts/createBenchmark.ts b/scripts/createBenchmark.ts index 6148d80bf2c..254c0d99af0 100644 --- a/scripts/createBenchmark.ts +++ b/scripts/createBenchmark.ts @@ -30,7 +30,7 @@ var rawCompilerSources = ""; sourceFiles.forEach(f=> { rawCompilerSources += "\r\n" + fs.readFileSync(path.join(tsSourceDir, f)).toString(); }); -var compilerSoruces = `var compilerSources = ${JSON.stringify(rawCompilerSources) };`; +var compilerSources = `var compilerSources = ${JSON.stringify(rawCompilerSources) };`; // .js code for the compiler, what we are actually testing var rawCompilerJavaScript = fs.readFileSync(path.join(tsBuildDir, "tsc.js")).toString(); @@ -38,7 +38,7 @@ rawCompilerJavaScript = rawCompilerJavaScript.replace("ts.executeCommandLine(ts. // lib.d.ts sources var rawLibSources = fs.readFileSync(path.join(tsBuildDir, "lib.d.ts")).toString(); -var libSoruces = `var libSources = ${JSON.stringify(rawLibSources) };`; +var libSources = `var libSources = ${JSON.stringify(rawLibSources) };`; // write test output if (!fs.existsSync(testOutputDir)) { @@ -48,7 +48,7 @@ if (!fs.existsSync(testOutputDir)) { // 1. compiler ts sources, used to test fs.writeFileSync( path.join(testOutputDir, "compilerSources.js"), - `${ compilerSoruces } \r\n ${ libSoruces }`); + `${ compilerSources } \r\n ${ libSources }`); // 2. the compiler js sources + a call the compiler fs.writeFileSync( diff --git a/scripts/generateLocalizedDiagnosticMessages.ts b/scripts/generateLocalizedDiagnosticMessages.ts index a9183c8ddbe..98bae170baa 100644 --- a/scripts/generateLocalizedDiagnosticMessages.ts +++ b/scripts/generateLocalizedDiagnosticMessages.ts @@ -1,177 +1,177 @@ -import * as fs from "fs"; -import * as path from "path"; -import * as xml2js from "xml2js"; - -function main(): void { - const args = process.argv.slice(2); - if (args.length !== 3) { - console.log("Usage:"); - console.log("\tnode generateLocalizedDiagnosticMessages.js "); - return; - } - - const inputPath = args[0]; - const outputPath = args[1]; - const diagnosticsMapFilePath = args[2]; - - // generate the lcg file for enu - generateLCGFile(); - - // generate other langs - fs.readdir(inputPath, (err, files) => { - handleError(err); - files.forEach(visitDirectory); - }); - - return; - - function visitDirectory(name: string) { - const inputFilePath = path.join(inputPath, name, "diagnosticMessages", "diagnosticMessages.generated.json.lcl"); - - fs.readFile(inputFilePath, (err, data) => { - handleError(err); - xml2js.parseString(data.toString(), (err, result) => { - handleError(err); - if (!result || !result.LCX || !result.LCX.$ || !result.LCX.$.TgtCul) { - console.error("Unexpected XML file structure. Expected to find result.LCX.$.TgtCul."); - process.exit(1); - } - const outputDirectoryName = getPreferedLocaleName(result.LCX.$.TgtCul).toLowerCase(); - if (!outputDirectoryName) { - console.error(`Invalid output locale name for '${result.LCX.$.TgtCul}'.`); - process.exit(1); - } - writeFile(path.join(outputPath, outputDirectoryName, "diagnosticMessages.generated.json"), xmlObjectToString(result)); - }); - }); - } - - /** - * A locale name is based on the language tagging conventions of RFC 4646 (Windows Vista - * and later), and is represented by LOCALE_SNAME. - * Generally, the pattern - is used. Here, language is a lowercase ISO 639 - * language code. The codes from ISO 639-1 are used when available. Otherwise, codes from - * ISO 639-2/T are used. REGION specifies an uppercase ISO 3166-1 country/region identifier. - * For example, the locale name for English (United States) is "en-US" and the locale name - * for Divehi (Maldives) is "dv-MV". - * - * If the locale is a neutral locale (no region), the LOCALE_SNAME value follows the - * pattern . If it is a neutral locale for which the script is significant, the - * pattern is -