スキップしてメイン コンテンツに移動

[TypeScript] Read source code of tsc 1(Parse options)

Intro

Because I want to know how to save the changes for the "incremental" option, I try to read the source code.
They are in the TypeScript project.

GitHub - microsoft/TypeScript: TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

This time, I write about how to parse the tsc command from src > tsc > tsc.ts.
According to the tsconfig.json of tsc, it refers "compiler" and "executeCommandLine".

tsc/tsconfig.json


    "references": [
        { "path": "../compiler", "prepend": true },
        { "path": "../executeCommandLine", "prepend": true }
    ]

tsc.ts and sys.ts

https://github.com/microsoft/TypeScript/blob/master/src/tsc/tsc.ts

In this code, I think the most important for understanding how to parse the command is here.

tsc/tsc.ts


ts.executeCommandLine(ts.sys, ts.noop, ts.sys.args);

The first argument had system info of Node.js.
https://github.com/microsoft/TypeScript/blob/master/src/compiler/sys.ts

The second one "ts.noop" does nothing as its name suggests.

compiler/core.ts


...
    /** Does nothing. */
    export function noop(_?: {} | null | undefined): void { }
...

command-line arguments

The last one is command-line arguments.
It comes from "process.argv.slice(2)".
For example, when I execute the JavaScript code by Node.js...

sample.js


"use strict";
function call() {
    console.log(process.argv);
}
call();

node dist/main.js -hello --world
I can get the result like below.
[
  '/usr/bin/node',
  '/home/example/Documents/workspace/ts-commandline-sample/dist/main.js',
  '-hello',
  '--world'
]
And by "slice(2)", I can get them(get from the third element to the end).
Array.prototype.slice() - JavaScript | MDN
[ '-hello', '--world' ]

executeCommandLine.ts

This class checks if the operation is for build or not, and (for this time) builds the scripts.

compiler/executeCommandLine.ts


...
    export function executeCommandLine(
        system: System,
        cb: ExecuteCommandLineCallbacks,
        commandLineArgs: readonly string[],
        maxNumberOfFilesToIterateForInvalidation?: number
    ) {
        if (isBuild(commandLineArgs)) {
            const { buildOptions, watchOptions, projects, errors } = parseBuildCommand(commandLineArgs.slice(1));
            if (buildOptions.generateCpuProfile && system.enableCPUProfiler) {
... 
            }
            else {
                return performBuild(
                    system,
                    cb,
                    buildOptions,
                    watchOptions,
                    projects,
                    errors
                );
            }
        }
...

isBuild

compiler/executeCommandLine.ts


...
    export function isBuild(commandLineArgs: readonly string[]) {
        if (commandLineArgs.length > 0 && commandLineArgs[0].charCodeAt(0) === CharacterCodes.minus) {
            const firstOption = commandLineArgs[0].slice(commandLineArgs[0].charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase();
            return firstOption === "build" || firstOption === "b";
        }
        return false;
    }
...

This function split the first option with minus symbol(0x2D) and checks if the remaining charactors are "build" or "b" or not.
So according to this, I can understand these 2 things.
  1. I can "tsc -b -w" but I can't "tsc -w -b" for building scripts.
  2. I also can "tsc --b" for building scripts.

Get build options for parsing other options

After understanding this operation is for building, tsc gets build options for parsing other options.

compiler/executeCommandLine.ts


const { buildOptions, watchOptions, projects, errors } = parseBuildCommand(commandLineArgs.slice(1));

For preparing, tsc gets the lists of CommandLineOption include "incremental" and create Map.

compiler/commandLineParser.ts


...
    /* @internal */
    export const commonOptionsWithBuild: CommandLineOption[] = [
...
        {
            name: "incremental",
            shortName: "i",
            type: "boolean",
            category: Diagnostics.Basic_Options,
            description: Diagnostics.Enable_incremental_compilation,
            transpileOptionValue: undefined
        },
...

Project References · TypeScript

compiler/commandLineParser.ts


...
    /*@internal*/
    export function createOptionNameMap(optionDeclarations: readonly CommandLineOption[]): OptionsNameMap {
        const optionsNameMap = createMap<CommandLineOption>();
        const shortOptionNames = createMap<string>();
        forEach(optionDeclarations, option => {
            optionsNameMap.set(option.name.toLowerCase(), option);
            if (option.shortName) {
                shortOptionNames.set(option.shortName, option.name);
            }
        });

        return { optionsNameMap, shortOptionNames };
    }
...

The function of createMap just creates Map<T>.

Parse build options

tsc gets the option value as same as checking if the operation is for building.

compiler/commandLineParser.ts


    /*@internal*/
    export function parseCommandLineWorker(
        diagnostics: ParseCommandLineWorkerDiagnostics,
        commandLine: readonly string[],
        readFile?: (path: string) => string | undefined) {
...
        function parseStrings(args: readonly string[]) {
            let i = 0;
            while (i < args.length) {
                const s = args[i];
                i++;
                if (s.charCodeAt(0) === CharacterCodes.at) {
                    parseResponseFile(s.slice(1));
                }
                else if (s.charCodeAt(0) === CharacterCodes.minus) {
                    const inputOptionName = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1);
                    const opt = getOptionDeclarationFromName(diagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true);
                    if (opt) {
                        i = parseOptionValue(args, i, diagnostics, opt, options, errors);
                    }
                    else {
...

After parsing the option values, tsc put them into "options".

compiler/commandLineParser.ts


...
    function parseOptionValue(
        args: readonly string[],
        i: number,
        diagnostics: ParseCommandLineWorkerDiagnostics,
        opt: CommandLineOption,
        options: OptionsBase,
        errors: Diagnostic[]
    ) {
        if (opt.isTSConfigOnly) {
            const optValue = args[i];
            if (optValue === "null") {
                options[opt.name] = undefined;
                i++;
            }
            else if (opt.type === "boolean") {
                if (optValue === "false") {
                    options[opt.name] = false;
                    i++;
                }
                else {
                    if (optValue === "true") i++;
                    errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line, opt.name));
                }
            }
            else {
                errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line, opt.name));
                if (optValue && !startsWith(optValue, "-")) i++;
            }
        }
        else {
            // Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
            if (!args[i] && opt.type !== "boolean") {
                errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt)));
            }

            if (args[i] !== "null") {
                switch (opt.type) {
                    case "number":
                        options[opt.name] = parseInt(args[i]);
                        i++;
                        break;
                    case "boolean":
                        // boolean flag has optional value true, false, others
                        const optValue = args[i];
                        options[opt.name] = optValue !== "false";
                        // consume next argument as boolean flag value
                        if (optValue === "false" || optValue === "true") {
                            i++;
                        }
                        break;
...
                }
            }
            else {
                options[opt.name] = undefined;
                i++;
            }
        }
        return i;
    }
...


Next time, I will read about performing build.

コメント

このブログの人気の投稿

[Angular][ASP.NET Core] Upload chunked files

Intro I wanted to send files to Web application (made by ASP.NET Core). If the file size had been small, I didn't need do any special things. But when I tried to send a large file, the error was occurred by ASP.NET Core's limitation. Though I could change the settings, but I didn't want to do that, because I hadn't known the file sizes what would been actually using. So I splitted the data into chunks first, and sent them. After receiving all chunks, I merged them into one file. There might be some libraries or APIs (ex. Stream API) what did them automatically, but I couldn't find them. What I did [ASP.NET Core] Make CORS enabled [Angular] Split a large file into chunks [Angular][ASP.NET Core] Send and receive data as form data [ASP.NET Core] Merge chunks into one file [ASP.NET Core] Make CORS enabled Because the client side application(Angular) and the server side application(ASP.NET Core) had been separated, I had to make CORS(Cross-Origin Requests) ...

[Nest.js] Show static files

Intro I wanted to use Nest.js and WebRTC(node-webrtc). NestJS - A progressive Node.js framework Documentation | NestJS - A progressive Node.js framework And because I wanted to try with simple page(not use JavaScript frameworks), I added static HTML, CSS, JavaScript into a Nest.js project. Prepare Install First, I installed @nestjs/cli. First steps | NestJS - A progressive Node.js framework As same as last time , I couldn't do global install because I had used Volta. But I could installed by volta. volta install @nestjs/cli Create project nest new nest-web-rtc-sample volta pin node@12 Run npm start After doing "npm start", I could getting "Hello World!" from http://localhost:3000. Add static files I could add static files by two ways. @nestjs/serve-static First one of them was using "serve-static". Serve Static | NestJS - A progressive Node.js framework npm install --save @nestjs/serve-static And I needed adding a module into app.modu...

[Angular] Sending file with Observable and showing loading screen

Intro When I tried sending file data on last time, I had confused with "promise.then", "async/await" and "Observable". [Angular][ASP.NET Core] Upload chunked files So I wanted to distinct them, and this time, I tried to use "Observable" because HttpClient return Observable<any>. Call observables in order I sended file in these steps. Read file by FileReader Create directory for saving chunks send and saving chunks merge chunks to one file and delete chunks Each steps used the former steps result. So I could write by Promise.then like below. this.executeStep1() // return Promise<UploadResult> .then(result => this.executeStep2(result)) // return Promise<UploadResult> .then(result => this.executeStep3(result)) // return Promise<UploadResult> .catch(reason => console.log(reason)); Result I could write with pipe & flatMap. file-uploader.service.ts public upload(file: File): Observable<U...