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

[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.

コメント

このブログの人気の投稿

[TypeScript] Try React 1

Intro This time I tried React. First, I referred Documents and Tutorial. React – A JavaScript library for building user interfaces GitHub - facebook/react: A declarative, efficient, and flexible JavaScript library for building user interfaces. Getting started with React - Learn web development | MDN Create a project To create a new React project, I used "Create React App". GitHub - facebook/create-react-app: Set up a modern web app by running one command. It created these files. .git node_modules public L favicon.ico L index.html L logo192.png L logo512.png L manifest.json L robots.txt src L App.css L App.test.tsx L App.tsx L index.css L index.test.tsx L index.tsx L logo.svg L react-app-env.d.ts L serviceWorker.ts L setupTests.ts .gitignore package.json package-lock.json README.md tsconfig.json Make the page work with IE11 I could view the page by "npm start" or "npm run start".   ...

[PostgreSQL] Play with TypeORM 1

Intro This time, I tried accessing Database by TypeORM. Because I wanted to manage Database tables by ORM, I created a table first.  GitHub - typeorm/typeorm   TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Installation and creating a project Installation TypeORM needed "reflect-metadata" and database driver. npm install --save typeorm reflect-metadata pg typescript tsc npm install --save-dev @types/node And I also installed "ts-node" to skip compiling. npm install --save ts-node Preparing References I could generate a TypeORM project by TypeORM command. npx typeorm init --name gen-typeorm-sample --database postgres I refered Most of all settings from it. Adding files and folders First, I added tsconfig.json. npx tsc --init And I edited like below. tsconfig.json { "compilerOptions": { /* Basic Options */ "incremental": true, "target": "es5", "module": "comm...

[Angular 10 + NgRx + Nest.js] Create project

Intro This time, I added server-side. I choosed Nest.js because I love TypeScript and I had been interested in using TypeScript in server-side programming. Environments Angular ver.10.0.2 @ngrx/store ver.9.2.0 @ngrx/effects ver.9.2.0 @nestjs/ng-universal ver.2.0.1 Couldn't add @ngrx/store into the Angular 10 project First, I created an Angular project and tried adding @ngrx/store into it. And I got an error. Installing packages for tooling via npm. Installed packages for tooling via npm. The package that you are trying to add does not support schematics. You can try using a different version of the package or contact the package author to add ng-add support. According to the issue below, I should add "@latest". npx ng add @ngrx/store@latest Because if I didn't add it, the @ngrx/store version was too low.    cannot use ng add to install ngrx store · Issue #2604 · ngrx/platform · GitHub Though I didn't know I should add "@latest" into ...