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

[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] Use WebSocket with ws

Intro Until last time , I had used node-web-rtc to try WebRTC. But because the example was a little complicated for I understood the core functions of using WebRTC. So I look for other frameworks or libraries. PeerJS is a famous library for WebRTC. peers/peerjs: Peer-to-peer data in the browser. - GitHub peers/peerjs-server: Server for PeerJS - GitHub PeerJS - Simple peer-to-peer with WebRTC A problem is I don't know how to integrate to the Nest.js project. I couldn't find examples. So I don't choose at least this time. What shall I choose? According MDN, WebRTC doesn't specify strictly what technology is used on server application for connecting two devices. Signaling and video calling - Web APIs | MDN But in many examples include MDN's one use WebSocket. samples-server/s/webrtc-from-chat at master · mdn/samples-server · GitHub So I try WebSocket in the Nest.js project. Use WebSocket in a Nest.js project Nest.js has a function for using We

[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