├── systems ├── refactor.md ├── testing │ ├── units.md │ ├── screenshots │ │ └── diff.png │ ├── README.md │ ├── baselines.md │ └── fourslash.md ├── debugging │ └── printing.md ├── vscode │ └── README.md ├── cli.md ├── sourcemaps.md ├── formatting │ └── formatting.md └── codefixes.md ├── codebase ├── src │ ├── services │ │ ├── formatting.md │ │ ├── textChanges.md │ │ └── completions.md │ └── compiler │ │ ├── services.md │ │ ├── utils.md │ │ ├── faq.md │ │ ├── parser.md │ │ ├── types.md │ │ ├── emitter.md │ │ ├── scanner.md │ │ ├── checker.md │ │ ├── checker-widening-narrowing.md │ │ ├── binder.md │ │ └── checker-inference.md └── screenshots │ ├── snippet-vscode.png │ └── threeslash-refs.png ├── intro ├── imgs │ ├── layers-2.png │ └── compiler-linear.png └── README.md ├── README.md ├── .vscode └── settings.json ├── additional_tooling.md ├── first_steps.md └── GLOSSARY.md /systems/refactor.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /systems/testing/units.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /codebase/src/services/formatting.md: -------------------------------------------------------------------------------- 1 | ### 2 | -------------------------------------------------------------------------------- /intro/imgs/layers-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/typescript-notes/HEAD/intro/imgs/layers-2.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED typescript-notes 2 | 3 | Moved to https://github.com/microsoft/TypeScript-Compiler-Notes 4 | -------------------------------------------------------------------------------- /intro/imgs/compiler-linear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/typescript-notes/HEAD/intro/imgs/compiler-linear.png -------------------------------------------------------------------------------- /systems/testing/screenshots/diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/typescript-notes/HEAD/systems/testing/screenshots/diff.png -------------------------------------------------------------------------------- /codebase/screenshots/snippet-vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/typescript-notes/HEAD/codebase/screenshots/snippet-vscode.png -------------------------------------------------------------------------------- /codebase/screenshots/threeslash-refs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orta/typescript-notes/HEAD/codebase/screenshots/threeslash-refs.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.printWidth": 114, 3 | "prettier.proseWrap": "always", 4 | "editor.formatOnType": true, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "editor.formatOnSave": true 7 | } -------------------------------------------------------------------------------- /additional_tooling.md: -------------------------------------------------------------------------------- 1 | ### Things to Make Your Life Easier 2 | 3 | Run the eslint auto-fixer before a commit. This takes a bit of time, but is less time then going back and fixing 4 | something because you missed a semi. 5 | 6 | ```sh 7 | echo 'gulp lint --fix' > .git/hooks/pre-commit 8 | ``` 9 | -------------------------------------------------------------------------------- /codebase/src/compiler/services.md: -------------------------------------------------------------------------------- 1 | ## Services 2 | 3 | `services` is effectively the place where all the IDE and TS meet. It it a series of files which power the 4 | LSP-like TSServer. 5 | 6 | The services are APIs are used by TSServer, which creates a `ts.server` SessionClient in `src/harness/client.ts` 7 | (it seems most of the `class`es in the compiler live in the server/services space. Maybe a by-product of working 8 | tightly with the VS Code team? ) 9 | -------------------------------------------------------------------------------- /codebase/src/compiler/utils.md: -------------------------------------------------------------------------------- 1 | ### Util Functions 2 | 3 | Some essentials: 4 | 5 | - [`findAncestor`][0] 6 | 7 | > Iterates through the parent chain of a node and performs the callback on each parent until the callback returns 8 | > a truthy value, then returns that value. 9 | > 10 | > If no such value is found, it applies the callback until the parent pointer is undefined or the callback returns 11 | > "quit" At that point findAncestor returns undefined. 12 | 13 | Basically looks up the AST until it finds something which passes. 14 | -------------------------------------------------------------------------------- /codebase/src/compiler/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## How can I find out if a type is `number[]`? 4 | 5 | ```ts 6 | getElementTypeOfArrayType(t) === numberType 7 | ``` 8 | 9 | `getElementTypeOfArrayType` returns undefined if `t` is not an array type. 10 | Use `isArrayType` or `isArrayLikeType` if that's all you need to know. 11 | 12 | ## How can I delete nodes in a transformer? 13 | 14 | Probably you return `undefined` instead of a new or existin node, but look at src/compiler/transformers/ts.ts. 15 | Deleting type annotations is its main job. 16 | 17 | -------------------------------------------------------------------------------- /codebase/src/compiler/parser.md: -------------------------------------------------------------------------------- 1 | # Parser 2 | 3 | At a measly 8k lines long, the Parser is responsible for controlling a scanner (or two) and turning the output 4 | tokens from the scanner into an AST as the canonical representation of the source file. 5 | 6 | ## JSDoc 7 | 8 | ## Context 9 | 10 | Because the parser itself is effectively a state machine which creates nodes from scanning text there is some 11 | reasonable dancing 12 | 13 | 14 | 15 | [0]: 16 | [4]: GLOSSARY.md#statements 17 | 18 | 19 | 20 | ` 21 | -------------------------------------------------------------------------------- /systems/testing/README.md: -------------------------------------------------------------------------------- 1 | ### Tests for TypeScript 2 | 3 | Yep, TypeScript has tests. Quite a lot, with a few different techniques: 4 | 5 | - [Unit Tests](./units.md) 6 | - [Baselines](./baselines.md) 7 | - [Fourslash](./fourslash.md) 8 | 9 | ## Commands worth knowing 10 | 11 | You run tests via `gulp runtests`. 12 | 13 | Flags worth knowing: 14 | 15 | - `--failed` - re-runs the failed tests only 16 | - `--no-lint` - don't run the linter when it completes 17 | - `-i` - Use the inspector to debug 18 | 19 | If you have a fail in the baselines: 20 | 21 | - `gulp diff` - to see the differences 22 | - `gulp baseline-accept` to overwrite the current baseline snapshots 23 | -------------------------------------------------------------------------------- /systems/debugging/printing.md: -------------------------------------------------------------------------------- 1 | ## For `console.log` debugging 2 | 3 | A lot of the time to see what the code you're representing you want to look in either `declaration` or 4 | `declarations`. These are Node subclasses which means they'll have a copy of `__debugGetText()` which should get 5 | you to the syntax representation. 6 | 7 | ### An XXYYFlags 8 | 9 | [Run](https://twitter.com/atcb/status/1174747774761324544) `ts.Debug.format[Whatever]Flags(flag)` in the console 10 | to find out the constituent parts 11 | 12 | ### `Type` 13 | 14 | Useful: `console.log(type.__debugKind)`. 15 | 16 | Chances are through you want to go through symbol though 17 | 18 | ### `Symbol` 19 | 20 | Using `symbolToString(symbol)` could get you somewhere, but so far that's not been 21 | 22 | ### `Node` 23 | 24 | ### `Signature` 25 | 26 | `signature.declaration.__debugGetText()` 27 | -------------------------------------------------------------------------------- /systems/vscode/README.md: -------------------------------------------------------------------------------- 1 | ### VS Code + TypeScript 2 | 3 | VS Code has three extensions related to TypeScript: 4 | 5 | - `extensions/typescript` - this is an `npm install`'d version of TypeScript. 6 | - `extensions/typescript-basics` - this is a extension which provides 7 | [language grammars and defines the language of TypeScript](https://github.com/microsoft/vscode/commit/e23c58b3aba76f25bb99400619d39f285eeec9e1#diff-cdbcc33fea0f5bd15137cf1750d69776) 8 | inside VS Code 9 | - `extensions/typescript-language-features` - this extends the TypeScript language support with commands, 10 | auto-complete et al. 11 | 12 | ### [`typescript-language-features`](https://github.com/microsoft/vscode/tree/master/extensions/typescript-language-features) 13 | 14 | A large amount of the work happens in the [`TypeScriptServiceClient`][1] which is the VS Code side of the 15 | TSServer. 16 | 17 | [1]: 18 | https://github.com/microsoft/vscode/blob/master/extensions/typescript-language-features/src/typescriptServiceClient.ts#L75 19 | -------------------------------------------------------------------------------- /systems/cli.md: -------------------------------------------------------------------------------- 1 | ## CLI 2 | 3 | Oddly, the TS Config is probably best described by the CLI which you can find in 4 | `src/compiler/commandLineParser.ts`. 5 | 6 | The CLI starts after parsing [`tsc.ts`][0] which triggers [`executeCommandLine`][1]. This function handles going 7 | from an arg list to structured data in the form of [ParsedCommandLine][2]. 8 | 9 | The actual work occurs in [`parseCommandLineWorker`][] which sets up some variables: 10 | 11 | ```ts 12 | const options = {} as OptionsBase; 13 | const fileNames: string[] = []; 14 | const errors: Diagnostic[] = []; 15 | ``` 16 | 17 | Then it starts If the letter is a - then start looking forwards to see if the arg is available in [the 18 | optionmap][4] 19 | 20 | function executeCommandLine 21 | 22 | [0]: src/tsc/tsc.ts 23 | [1]: 24 | [2]: 25 | [3]: 26 | [4]: 27 | -------------------------------------------------------------------------------- /systems/sourcemaps.md: -------------------------------------------------------------------------------- 1 | ### Nodes 2 | 3 | Every node in the TS AST has an optional `emitNode`, this node provides a sort of wrapping context to a node when 4 | it is emitted. For example it keeps track of trailing/leading comments and importantly for sourcemaps: 5 | `sourceMapRange` and `tokenSourceMapRange`. 6 | 7 | These are applied via code transformers in TypeScript 8 | 9 | ### Creating the Files 10 | 11 | Files are created inside `emitter.ts` - the opening function being `emitWorker`. 12 | 13 | - Each source file then gets `writeFile` called on it which triggers the AST traversal on the source file 14 | - A `sourceMapGenerator` is created 15 | - Each node gets triggered via `pipelineEmit` 16 | - `pipelineEmit` runs all transformers against the node 17 | - This is where the pipeline-ing for TS -> ES2020 -> ES2018 -> ES2017 -> ES2015 -> ES6 happens to that node 18 | - Each node calls `emitSourcePos` which tells the `sourceMapGenerator` that a mapping exists for this node 19 | 20 | This means when looking into how TS handles sourcemap, you need to find the part of the above pipeline which 21 | transforms the syntax you're looking for. 22 | -------------------------------------------------------------------------------- /systems/testing/baselines.md: -------------------------------------------------------------------------------- 1 | # Baselines 2 | 3 | Baseline tests are effectively large folder-based snapshot tests. Like [fourslash](./fourslash), these tests are 4 | somewhat integration level. These test generally test the internals of the compiler, they do this by you creating 5 | a TypeScript file where the snapshot result is a symbol map of all the symbols in that file and a list of all 6 | raised errors then compiling the tests. 7 | 8 | ### How to make a baseline 9 | 10 | 1. Make a new file: ` touch tests/cases/compiler/overloadFunctionsNotEqualToGenerics.ts`. Add some TypeScript to 11 | it. 12 | 2. d 13 | 14 | ### How to amend a baseline 15 | 16 | `gulp runtests` will run all the baselines eventually. Or `gulp runtests -i --tests=baseline` should speed things 17 | up if you only want to see those specific changes. 18 | 19 | All of the baselines exist in `tests/baselines/local/`. They kinda look like: 20 | 21 | ```sh 22 | $ tree tests/baselines/local/ 23 | tests/baselines/local/ 24 | └── api 25 | ├── tsserverlibrary.d.ts 26 | └── typescript.d.ts 27 | 28 | 1 directory, 2 files 29 | ``` 30 | 31 | This lets you know that two tests have changed from what you expect. They live in `local/api`, so you can compare 32 | the folders. I normally use [Kaleidoscope](https://www.kaleidoscopeapp.com), but the team loves 33 | [Beyond Compare](https://scootersoftware.com) - it's a bit Windows-y but does a good job showing you how the 34 | folder infrastructure differs. 35 | 36 | ![./screenshots/diff.png](./screenshots/diff.png) 37 | 38 | Once you're happy with the new baselines, you can run `gulp baseline-accept` to move them into the codebase. You 39 | will be able to see your diffs in git now. :+1: 40 | -------------------------------------------------------------------------------- /codebase/src/compiler/types.md: -------------------------------------------------------------------------------- 1 | # Type Hierarchy 2 | 3 | Root class: `Type` 4 | 5 | ### How are properties stored and found on a type? 6 | 7 | `checkPropertyAccessExpressionOrQualifiedName` 8 | 9 | ### getDeclaredTypeOfSymbol vs getTypeOfSymbol 10 | 11 | The problem is that symbols can have both types and values associated with them: 12 | 13 | ```ts 14 | type A = number 15 | const A = "do not do this" 16 | ``` 17 | 18 | And the compiler needs a way to get the type of both the type and the const. 19 | So it uses `getDeclaredTypeOfSymbol` for types and `getTypeOfSymbol[AtLocation]` for values: 20 | 21 | ```ts 22 | getDeclaredTypeOfSymbol(A) == number 23 | getTypeOfSymbol(A) == string 24 | ``` 25 | 26 | Confusingly, classes (and enums and aliases) declare both a type and a value, so, a tiny bit arbitrarily, the instance side is the type and the static side is the value: 27 | 28 | ```ts 29 | class C { 30 | m() { } 31 | static s() { } 32 | } 33 | getTypeOfSymbol() == { new(): C, s(): void } == typeof C 34 | getDeclaredTypeOfSymbol() == { m(): void } == C 35 | ``` 36 | 37 | This kind of makes sense when you think about that C actually does when executed: it defines a value that is constructable. 38 | This leads to the "deconstructed class" pattern used in tricky situations, for example: 39 | 40 | ``` ts 41 | interface C { 42 | m(): void 43 | } 44 | var C: { 45 | new(): C 46 | s: void 47 | } 48 | ``` 49 | 50 | Again, it's a tiny bit arbitrary to choose the static side as the value, since ultimately you get a value from calling new C() too. 51 | But the deconstructed class pattern shows that you can get away with writing just a type for the instance side, whereas you must write a value for the static side. 52 | 53 | 54 | 55 | [1]: 58 | -------------------------------------------------------------------------------- /codebase/src/compiler/emitter.md: -------------------------------------------------------------------------------- 1 | ## Emitter 2 | 3 | The emitter is a tree based syntax emitter. It works by going through the TypeScript AST for a program and 4 | emitting source code as it is pipelined. 5 | 6 | The emitter itself is "dumb" in the sense that it doesn't contain logic outside of printing whatever AST it is 7 | given. So, it's possible that a bug in emission is actually that the AST isn't set up the way that you'd like it. 8 | 9 | ### Outfile 10 | 11 | Creating a single file which represents many is done by creating a `SyntaxKind.Bundle`. Printing happens in 12 | [`function writeBundle(`][0]. There are `prepends` which I don't understand, and then each sourcefile is is 13 | printed. 14 | 15 | ### Printer 16 | 17 | The printer is a part of the emitter, you create one with [`createPrinter`][1], then start calling [`print`][2] 18 | with an AST node on it. This adds the node via a [pipeline][3]: 19 | 20 | ```ts 21 | const enum PipelinePhase { 22 | Notification, 23 | Substitution, 24 | Comments, 25 | SourceMaps, 26 | Emit, 27 | } 28 | ``` 29 | 30 | With the word to start emitting through the AST in [`pipelineEmitWithHint`][4]. There is a hint option which can 31 | be used to force the emit type. 32 | 33 | ## Post Processing via Transformers 34 | 35 | The process of changing your AST into the expected JS or TS happens the emitter compiler transformers. There is a 36 | full step 37 | 38 | Emitting a declaration file is a multi-step process. It goes through the above emitter of its AST, but then _also_ 39 | goes through a 40 | 41 | 42 | [0]: 43 | [1]: 44 | [2]: 45 | [3]: 46 | [3]: 47 | 48 | -------------------------------------------------------------------------------- /systems/formatting/formatting.md: -------------------------------------------------------------------------------- 1 | # How does TypeScript formatting work? 2 | 3 | To format code you need to have a formatting context and a `SourceFile`. The formatting context contains all user 4 | settings like tab size, newline character, etc. 5 | 6 | The end result of formatting is represented by TextChange objects which hold the new string content, and the text 7 | to replace it with. 8 | 9 | ```ts 10 | export interface TextChange { 11 | span: TextSpan; // start, length 12 | newText: string; 13 | } 14 | ``` 15 | 16 | ## Internals 17 | 18 | Most of the exposed APIs internally are `format*` and they all set up and configure `formatSpan` which could be 19 | considered the root call for formatting. Span in this case refers to the range of the sourcefile which should be 20 | formatted. 21 | 22 | The formatSpan then uses a scanner (either with or without JSX support) which starts at the highest node the 23 | covers the span of text and recurses down through the node's children. 24 | 25 | As it recurses, `processNode` is called on the children setting the indentation is decided and passed through into 26 | each of that node's children. 27 | 28 | The meat of formatting decisions is made via `processPair`, the pair here being the current node and the previous 29 | node. `processPair` which mutates the formatting context to represent the current place in the scanner and 30 | requests a set of rules which can be applied to the items via `createRulesMap`. 31 | 32 | There are a lot of rules, which you can find in [rules.ts](./rules.ts) each one has a left and right reference to 33 | nodes or token ranges and note of what action should be applied by the formatter. 34 | 35 | ## Rules 36 | 37 | ### Where is this used? 38 | 39 | The formatter is used mainly from any language service operation that inserts or modifies code. The formatter is 40 | not exported publicly, and so all usage can only come through the language server. 41 | -------------------------------------------------------------------------------- /first_steps.md: -------------------------------------------------------------------------------- 1 | ### Getting started 2 | 3 | - Clone the repo: `git clone https://github.com/microsoft/TypeScript` 4 | - `cd TypeScript` 5 | - `npm i` 6 | - `npm run gulp` 7 | - `code .` then briefly come back to terminal to run 8 | - `npm test` 9 | 10 | OK, that should have a copy of the compiler up and running and then you have tests going in the background to 11 | prove that everything is working fine. This could take maybe 10m? There'll be a progress indicator. 12 | 13 | ### Getting set up 14 | 15 | To get your VS Code env working smoothly, set up the per-user config 16 | 17 | - Set up your `.vscode/launch.json` by running: `cp .vscode/launch.template.json .vscode/launch.json` 18 | - Set up your `.vscode/settings.json` by running: `cp .vscode/settings.template.json .vscode/settings.json` 19 | 20 | In the `launch.json` I duplicate the configuration, and change `"${fileBasenameNoExtension}",` to be whatever test 21 | file I am currently working on. 22 | 23 | ### Learn the debugger 24 | 25 | You'll probably spend a good amount of time in it, if this is completely new to you. Here is a video from 26 | [@alloy](https://github.com/alloy) covering all of the usage of the debugger inside VS Code and how it works. 27 | 28 | To test it out, open up `src/compiler/checker.ts` find `function checkSourceFileWorker(node: SourceFile) {` and 29 | add a debugger on the first line in the code. If you open up `tests/cases/fourslash/getDeclarationDiagnostics.ts` 30 | and then run your new debugging launch task `Mocha Tests (currently opened test)`. It will switch into the 31 | debugger and hit a breakpoint in your TypeScript. 32 | 33 | You'll probably want to add the following to your watch section in VS Code: 34 | 35 | - `node.__debugKind` 36 | - `node.__debugGetText()` 37 | - `source.symbol.declarations[0].__debugKind` 38 | - `target.symbol.declarations[0].__debugKind` 39 | 40 | This is really useful for keeping track of changing state, and it's pretty often that those are the names of 41 | things you're looking for. 42 | 43 | ### Getting started 44 | 45 | You can learn about the different ways to [write a test here](./systems/testing) to try and re-produce a bug. It 46 | might be a good idea to look in the recently closed PRs to find something small which adds a test case, then read 47 | the test and the code changes to get used to the flow. 48 | -------------------------------------------------------------------------------- /systems/testing/fourslash.md: -------------------------------------------------------------------------------- 1 | # Fourslash 2 | 3 | The fourslash tests are an integration level testing suite. By this point they have a very large API surface, and 4 | tend to cover a lot of the "user-facing" aspects of TypeScript. E.g. things which an IDE might have an interest in 5 | knowing. 6 | 7 | ### How to run one 8 | 9 | `gulp runtests` will run all the fourslash tests eventually. Or `gulp runtests -i --tests=[filename]` should speed 10 | things up if you only want to see those specific changes. 11 | 12 | ### How Fourslash runs a test 13 | 14 | Fourslash automatically generates mocha tests based on files you put inside [`/tests/cases/fourslash`][0] the code 15 | for this lives in [`/src/testRunner/fourslashRunner.ts`][1]. This class is instantiated in 16 | [`/src/testRunner/runner.ts`][2]. 17 | 18 | From here the main work all lives in [`/src/harness/foudslash.ts`][3] where we'll be spending the rest of this 19 | section. The initial entry point is [`runFourSlashTest`][4] but the work is in [`runFourSlashTestContent`][5]. 20 | 21 | This function first creates a virtual fs, uses [`parseTestData`][6] to fill up the virtual fs. `parseTestData`: 22 | 23 | - Loops through every line in the test file 24 | - If the line starts with `////` then it starts piping the text into a new string which represents the current 25 | file 26 | - If the line starts with `//` then check whether it's a special case variable (like `Filename`) - if it's not 27 | then it will get passed as though it were a TSConfig setting. 28 | 29 | This isn't `eval`-ing the code, so the tests under are ignored. Here's an example test file: 30 | 31 | ```ts 32 | /// 33 | // #29038 34 | 35 | // @allowJs: true 36 | // @checkJs: true 37 | 38 | // @Filename: /node_modules/moment.d.ts 39 | ////declare function moment(): void; 40 | ////export = moment; 41 | 42 | // @Filename: /b.js 43 | ////[|moment;|] 44 | 45 | goTo.file("/b.js"); 46 | verify.importFixAtPosition([ 47 | `import moment from "moment"; 48 | 49 | moment;` 50 | ]); 51 | ``` 52 | 53 | ### Formatting 54 | 55 | 56 | [0]: /src/testRunner/fourslashRunner.ts 57 | [1]: /tests/cases/fourslash 58 | [2]: /src/testRunner/runner.ts 59 | [3]: /src/harness/fourslash.ts 60 | [4]: 61 | [5]: 62 | [5]: 63 | 64 | -------------------------------------------------------------------------------- /codebase/src/services/textChanges.md: -------------------------------------------------------------------------------- 1 | # Text Changes 2 | 3 | The majority of this file is devoted to a class called the [`ChangeTracker`][0]. This class is nearly always 4 | created via `ChangeTracker.with` where you would give it a context object. 5 | 6 | Here is an example context object: 7 | 8 | ```ts 9 | { 10 | cancellationToken:CancellationTokenObject {cancellationToken: TestCancellationToken} 11 | errorCode:2304 12 | formatContext:Object {options: Object, getRule: } 13 | host:NativeLanguageServiceHost {cancellationToken: TestCancellationToken, settings: Object, sys: System, …} 14 | preferences:Object {} 15 | program:Object {getRootFileNames: , getSourceFile: , getSourceFileByPath: , …} 16 | sourceFile:SourceFileObject {pos: 0, end: 7, flags: 65536, …} 17 | span:Object {start: 0, length: 6} 18 | } 19 | ``` 20 | 21 | You only really see `ChangeTrack` in use within the codefixes and refactors given that the other case where 22 | TypeScript emits files is a single operation of emission. 23 | 24 | The change tracker keeps track of individual changes to be applied to a file. There are [currently][1] four main 25 | APIs that it works with: 26 | `type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode | ChangeText;` 27 | 28 | The `ChangeTrack` class is then used to provide high level API to describe the sort of changes you might want to 29 | make, which eventually fall into one of the four categories above. 30 | 31 | ### Making Changes 32 | 33 | The end result of using a `ChangeTrack` object is an array of `FileTextChanges` objects. The `ChangeTrack.with` 34 | function lets you work with a tracker instance elsewhere and passes back the `ChangeTrack` objects. 35 | 36 | The core work in generating changes occurs in: 37 | 38 | - [`getTextChangesFromChanges`][4] 39 | - [`computeNewText`][5] 40 | - [`getFormattedTextOfNode`][6] 41 | 42 | Going from an AST node to text is done by creating a [`printer`][7] in [`getNonformattedText`][8]. The printer 43 | returns an unformatted node, which is then ran through [a formatter][./formatting.md] and the raw string 44 | substitution is done in [`applyChanges`][9]. 45 | 46 | Changes look like this: 47 | 48 | ```ts 49 | [{ fileName: "/b.js", textChanges: [{ span: { start: 0, length: 0 }, newText: "// @ts-ignore\n" }] }]; 50 | ``` 51 | 52 | ### Writing 53 | 54 | [`newFileChanges`][3] handles passing the set of 55 | 56 | 57 | [0]: 58 | [1]: 59 | [2]: 60 | [3]: 61 | [4]: 62 | [5]: 63 | [6]: 64 | [7]: 65 | [8]: 66 | [8]: 67 | 68 | -------------------------------------------------------------------------------- /systems/codefixes.md: -------------------------------------------------------------------------------- 1 | ### How do Codefixes work 2 | 3 | A codefix is a signal from TypeScript to an editor that it's possible for TypeScript to provide an automated fix 4 | for some particular part of a codebase. 5 | 6 | Codefixes are implemented in a way that always requires there to be an error code, which means they bubble through 7 | TypeScript's internals initially from a compiler error. 8 | 9 | ### Codefix vs Refactor 10 | 11 | Code fixes have an associated error code, and can be skipped cheaply when trying to figure out if it's applicable. 12 | A refactor on the other hand does not come from an error code and is therefore somewhat always available, and are 13 | more expensive to check for. 14 | 15 | ### How are they used? 16 | 17 | The code fix assertions come in from comes in from the language service, via [`getCodeFixesAtPosition`][1], this 18 | says here's a file and a selection range for their selected text and any potential compiler error codes that touch 19 | section. 20 | 21 | The language service then reaches into [the codeFixProvider][2] via [`getFixes`][3], this delegates its work to 22 | [`getCodeActions`][4] which is a function which each codefix provides. 23 | 24 | These are returned to the IDE in the form of an object with: 25 | 26 | ```ts 27 | interface CodeAction { 28 | /** Description of the code action to display in the UI of the editor */ 29 | description: string; 30 | /** Text changes to apply to each file as part of the code action */ 31 | changes: FileTextChanges[]; 32 | /** 33 | * If the user accepts the code fix, the editor should send the action back in a `applyAction` request. 34 | * This allows the language service to have side effects (e.g. installing dependencies) upon a code fix. 35 | */ 36 | commands?: CodeActionCommand[]; 37 | 38 | /** Short name to identify the fix, for use by telemetry. */ 39 | fixName: string; 40 | /** 41 | * If present, one may call 'getCombinedCodeFix' with this fixId. 42 | * This may be omitted to indicate that the code fix can't be applied in a group. 43 | */ 44 | fixId?: {}; 45 | fixAllDescription?: string; 46 | } 47 | ``` 48 | 49 | An example of one is: 50 | 51 | ```ts 52 | const codefix = { 53 | description: `Import 'moment' from module "moment"`, 54 | fixAllDescription: "Add all missing imports", 55 | fixId: "fixMissingImport", 56 | fixName: "import" 57 | }; 58 | ``` 59 | 60 | ### Testing a code fix 61 | 62 | You can use fourslash to set up a project that emulates the sort of before and after experience from your codefix 63 | 64 | ```ts 65 | /// 66 | // #29038 67 | 68 | // @allowJs: true 69 | // @checkJs: true 70 | // @esModuleInterop: true 71 | // @moduleResolution: node 72 | 73 | // @Filename: /node_modules/moment.d.ts 74 | ////declare function moment(): void; 75 | ////export = moment; 76 | 77 | // @Filename: /b.js 78 | ////[|moment;|] 79 | 80 | goTo.file("/b.js"); 81 | verify.importFixAtPosition([ 82 | `import moment from "moment"; 83 | 84 | moment;` 85 | ]); 86 | ``` 87 | 88 | ### Example codefix PRs 89 | 90 | These are some reference PRs to look at 91 | 92 | - https://github.com/microsoft/TypeScript/pull/23711 93 | - https://github.com/microsoft/TypeScript/pull/32281 94 | 95 | 96 | [1]: 97 | [2]: src/services/codeFixProvider.ts 98 | [3]: 99 | [4]: 100 | 101 | -------------------------------------------------------------------------------- /codebase/src/compiler/scanner.md: -------------------------------------------------------------------------------- 1 | # Scanner 2 | 3 | One of the smallest parts of the compiler's critical path to AST then type-checking. It exists to create a stream 4 | of syntax tokens for use by another object. The scanner gets most of its usage via a [parser][0] instance. 5 | 6 | ## Overview 7 | 8 | You create a Scanner instance via [`createScanner`][1], there are two main modes of scanning: Standard and JSX. 9 | Then there are specific functions for scanning inside JSDoc comments. 10 | 11 | First you set the text of a scanner to be your JS source code via [`setText`][2], this takes the source code and 12 | an optional start and end range in which you want to scan. This range is an important part of what keeps 13 | TypeScript's server mode fast via [incremental parsing][3]. 14 | 15 | At a high level, the scanner works by a having another object call [`scanner.scan`][4] this: 16 | 17 | - Pulls out the character at the current position (`pos`) in the text 18 | - Runs a very big switch statement against known characters which start syntax 19 | - Then move look forwards till the end of that list of chars to decode if it's what we think it is. here's an 20 | example of what happens when [the character found][5] is a `!`: 21 | 22 | ```ts 23 | case CharacterCodes.exclamation: 24 | // Look to see if it's "!=" 25 | if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { 26 | // Also check if it's "!==" 27 | if (text.charCodeAt(pos + 2) === CharacterCodes.equals) { 28 | // for !== move the position forwards to the end of the symbol 29 | // then set the token 30 | return pos += 3, token = SyntaxKind.ExclamationEqualsEqualsToken; 31 | } 32 | // Move it two, set the token 33 | return pos += 2, token = SyntaxKind.ExclamationEqualsToken; 34 | } 35 | 36 | // Move forwards one character and set the token 37 | pos++; 38 | return token = SyntaxKind.ExclamationToken; 39 | ``` 40 | 41 | - A `SyntaxKind` is returned from [`scan`][4] and it's up to the scanner owner to do work with those tokens. The 42 | scanner keeps track of a few useful values for that: 43 | 44 | - `getStartPos` - where the token was started including preceding whitespace 45 | - `getTextPos` - the end position of the current token 46 | - `getTokenText` - the text between the token start and end 47 | - `getTokenValue` - some syntax contains a value which can be represented as a string, a good example is 48 | literally a string. The `"` or `'` are not included in the value. 49 | 50 | ## Trivia 51 | 52 | When creating a scanner you get to choose whether whitespace should be returned in the stream of tokens. This is 53 | nearly always off, but it is used inside the [formatter][6] and for syntax highlighting via the TSServer via a 54 | [classifier][7]. 55 | 56 | ## JSX 57 | 58 | Some of the more complicated aspects of JSX support is mostly handled back in [the parser][0], however JSX support 59 | in the scanner [uses specific syntax tokens][8]. 60 | 61 | ## Flags 62 | 63 | One way for the scanner to keep track of scan issues, or internal state is [via `TokenFlags`][9]. Any example of 64 | this is in scanning a number. TypeScript supports underscores in numbers `100_000`, when scanning a number literal 65 | if it detects a `CharacterCodes._` then the flag `TokenFlags.ContainsSeparator` is set and later on that is used 66 | to ensure the `tokenValue` is set correctly. 67 | 68 | ## Rescanning 69 | 70 | Because the scanner is only interested in passing out tokens as it sees them, it doesn't really have a memory of 71 | previous tokens. This means that occasionally the controlling object will need to rewind and re-run the scanner 72 | with a different type of context. This is called rescanning. 73 | 74 | ## Example code 75 | 76 | [Here's a scanner playground](https://5d39df23407c626e65aee7ef--ts-scanner-tokens.netlify.com) - adding TypeScript 77 | will show you the tokens generated by a single scanner. It's worth noting that this doesn't represent that 78 | _actual_ results of the scanner when using TypeScript, because the parser controls re-scanning and this playground 79 | doesn't do that. 80 | 81 | 82 | [0]: ./parser.md 83 | [1]: 84 | [2]: 85 | [3]: GLOSSARY.md#incremental-parsing 86 | [4]: 87 | [5]: 88 | [6]: ./formatter.md 89 | [7]: 90 | [8]: 91 | [9]: 92 | 93 | -------------------------------------------------------------------------------- /codebase/src/services/completions.md: -------------------------------------------------------------------------------- 1 | # Completions 2 | 3 | 4 | Completions for TypeScript and JavaScript are provided by TypeScript's language service. 5 | When you are in the middle of typing something in an editor (or if you hit Ctrl + Space in VSCode), the editor sends a request to the TypeScript language service. 6 | Completions is responsible for answering that request with suggestions to *complete* what you are typing. 7 | 8 | 9 | ## Overview 10 | 11 | Most of the implementation lives in the `src/services/completions.ts` file, and there are several steps the implementation goes through to answer a completion request. 12 | 13 | The entry point into completions is `getCompletionsAtPosition()`. 14 | As the name suggests, this function takes a `SourceFile` and a `position` as arguments (among other things), and it returns a [`CompletionInfo`](https://github.com/microsoft/TypeScript/blob/404a7d602df9c19d98d49e6a6bf2295e423be676/src/services/types.ts?#L1172-L1191) object with completions for that specific position. 15 | 16 | `CompletionInfo` has a few different properties, but we're mainly interested in the `entries: CompletionEntry[]` property, because a [`CompletionEntry`](https://github.com/microsoft/TypeScript/blob/404a7d602df9c19d98d49e6a6bf2295e423be676/src/services/types.ts?#L1220-L1249) encodes the suggestions returned. 17 | 18 | 19 | ### Completion entry 20 | 21 | Some `CompletionEntry` properties and what they mean: 22 | 23 | * **name**: the name of that completion entry. Usually if the completion is for an identifier/keyword named `foo`, then the name of the entry is also going to be `foo`. 24 | * **insertText**: the text that is going to be inserted in the file, at the completion position, when the user accepts the suggestion corresponding to this completion entry. 25 | `insertText` is optional, and if it is not present, then `name` is the text that is inserted instead. 26 | * **isSnippet**: if this is true, then this completion entry is a snippet, and `insertText` is a snippet text. 27 | e.g.: 28 | A completion snippet for declaring a method `foo`, with a tab stop (`${0}`) in its body: 29 | ```ts 30 | { 31 | isSnippet: true, 32 | insertText: "foo() { ${0} }", 33 | } 34 | ``` 35 | becomes this in VSCode, when accepted (note the cursor position): 36 | ![Screenshot of vscode with code `class Foo { foo() { | } }` in it.](../../screenshots/snippet-vscode.png) 37 | For more on snippets, see [Snippets in Visual Studio Code](https://code.visualstudio.com/docs/editor/userdefinedsnippets). 38 | * **replacementSpan**: the span (i.e. a continuous range) of the source file that is going to be *replaced* by the text inserted by this completion. It is optional, so we only need to provide this if we want to override the *default* replacement span for this completion entry. 39 | * **hasAction**: whether that completion requires additional actions if it is accepted. For instance, a completion might insert variables that need to be imported, so if that completion is accepted, it needs an additional action of inserting import statements. 40 | 41 | ## Implementation 42 | 43 | `getCompletionsAtPosition()` goes through a lot of steps and additional function calls before returning a `CompletionInfo` response. 44 | Roughly the steps are: 45 | 1. call `getCompletionData` to gather the data needed to construct a `CompletionInfo`. 46 | `getCompletionData`'s returns data including a **list of symbols** for things (e.g. variables, properties) we may want to offer for completions. 47 | 2. We call the appropriate function for transforming the completion data into completion info. 48 | The exact function called depends on the the kind of data returned by `getCompletionData`, which can be: 49 | * `JSDoc`: JSDoc-specific completion data, 50 | * `Keywords`: keyword completion data, 51 | * `Data`: general data not falling into the above categories (aka everything else). 52 | 53 | If the data is of jsdoc kind, then we call `jsdocCompletionInfo`, if it is keyword data we call `specificKeywordCompletionInfo`. 54 | Most importantly, though, when we have the general kind of data, we proceed with **calling `completionInfoFromData`**. 55 | This is the flow you want to look at most of the time, so let's assume we are following this general flow. 56 | 3. `completionInfoFromData` is called with the data we got from `getCompletionData`. 57 | Mainly, it calls `getCompletionEntriesFromSymbols` to construct completion entries from the symbols obtained in `getCompletionData`. 58 | 59 | ### `getCompletionData` 60 | 61 | Step one is to grab [a `CompletionData`][1] via [`getCompletionData`][2]. This function tries to find a context 62 | token which first looks forwards, and then try find a `contextToken`. This is generally the preceding token to 63 | your cursor, as that tends to be the most important thing when deciding what to show next. This takes into account 64 | things like `x.y` and `y?.y` by diving deeper into preceding identifier. 65 | 66 | This dive to find a "responsible" item for a completion request called `node` in the code. 67 | 68 | Next it goes through the following checks for a set of completions. 69 | TODO: continue this. 70 | 71 | ### [`getCompletionEntriesFromSymbols`]((https://github.com/Microsoft/TypeScript/blob/340f81035ff1d753e6a1f0fedc2323d169c86cc6/src/services/completions.ts#L305)) 72 | 73 | Some completion scenarios require doing special work when transforming a symbol into a completion entry. 74 | That special work is done here, in `getCompletionEntriesFromSymbols`, when we call `createCompletionEntry`. 75 | 76 | As an example, let's walk through [class member snippet completions](https://github.com/microsoft/TypeScript/pull/46370), a completion scenario that suggests whole class member declarations (i.e. method and property declarations). 77 | In `createCompletionEntry`, we get the symbol for a class member, say a method `foo`, that we want to offer as a completion. First, we detect that this symbol is for a class member (i.e. method `foo`'s symbol). 78 | Then, to turn that symbol into a completion entry, we have to figure out what the `insertText` for the entry must be. 79 | For method `foo`'s completion entry, we decide the `insertText` is going to be the declaration for method `foo`, something like: 80 | ```ts 81 | foo(x: string): number { 82 | // empty implementation 83 | } 84 | ``` 85 | So, to get that custom `insertText`, `createCompletionEntry` calls [`getEntryForMemberCompletion`](https://github.com/microsoft/TypeScript/blob/404a7d602df9c19d98d49e6a6bf2295e423be676/src/services/completions.ts#L857). 86 | 87 | Another scenario that works similarly is import completions: in `createCompletionEntry`, we call [`getInsertTextAndReplacementSpanForImportCompletion`](https://github.com/microsoft/TypeScript/blob/404a7d602df9c19d98d49e6a6bf2295e423be676/src/services/completions.ts#L1118) to get the custom `insertText` for a completion for importing a symbol, for instance `import { foo } from "foo"`. 88 | 89 | ## String Literal Completions 90 | 91 | E.g. are you inside a string and asking for completions? TS differentiates between reference comments 92 | ([triple slash](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html)): 93 | 94 | ![../../screenshots/threeslash-refs.png](../../screenshots/threeslash-refs.png) 95 | 96 | And strings as a part of the AST. These have a 97 | [few](https://github.com/Microsoft/TypeScript/blob/340f81035ff1d753e6a1f0fedc2323d169c86cc6/src/services/stringCompletions.ts#L103) 98 | different uses: 99 | 100 | - They could be path references 101 | - They could be module references 102 | - They could be indexed keys from an object 103 | - They could be parts of a union object 104 | 105 | #### 106 | 107 | 108 | [1]: 109 | [2]: 110 | 111 | -------------------------------------------------------------------------------- /codebase/src/compiler/checker.md: -------------------------------------------------------------------------------- 1 | # Checker 2 | 3 | Ok, yeah, so it's a 40k LOC file. Why 40k lines in one file? Well there's a few main arguments: 4 | 5 | - All of the checker is in one place. 6 | - Save memory by making it a global, to quote a comment in the parser: 7 | 8 | > ``` 9 | > // Implement the parser as a singleton module. We do this for perf reasons because creating parser instances 10 | > // can actually be expensive enough to impact us on projects with many source files. 11 | > ``` 12 | 13 | Lots of these functions need to know a lot about each other, the top of the function `createTypeChecker` has a set 14 | of variables which are global within all of these functions and are liberally accessed. 15 | 16 | Switching to different files means probably making [god objects][god], and the checker needs to be extremely fast. 17 | We want to avoid additional calls for ambient context. There are architectural patterns for this, but it's better 18 | to assume good faith that they've been explored already (8 years down the line now.) 19 | 20 | Anyway, better to get started somewhere. I [asked online](https://twitter.com/orta/status/1148335807780007939) 21 | about how people would try to study a file like this and I think one of the best paths is by following a 22 | particular story as a file gets checked. 23 | 24 | ## An entry-point 25 | 26 | The likely entry point for type checking is via a Program. The program has a memoized typechecker created in 27 | [`getDiagnosticsProducingTypeChecker`][0] which creates a type checker. 28 | 29 | The initial start of type checking starts with [`getDiagnosticsWorker`][1], worker in this case isn't a threading 30 | term I believe ( at least I can't find anything like that in the code ) - it is set up to listen for diagnostic 31 | results (e.g. warns/fails) and then triggers [`checkSourceFileWorker`][2]. 32 | 33 | This function starts at the root `Node` of any TS/JS file node tree: `SourceFile`. It will then have to recurse 34 | through all of the [Syntax Tree][ast] nodes in it's tree. 35 | 36 | It doesn't start with a single recursive function though, it starts by looking through the SourceFile's 37 | [`statements`][4] and through each one of those to get all the nodes. For example: 38 | 39 | ```ts 40 | // Statement 1 41 | const hi = () => "Hello"; 42 | 43 | // Statement 2 44 | console.log(hi()); 45 | ``` 46 | 47 | Which looks a bit like: 48 | 49 | ```sh 50 | SourceFile 51 | statements: 52 | 53 | - VariableStatement 54 | - declarationList: VariableDeclarationList # (because any const can have many declarations in a row... ) 55 | - variables: VariableStatement 56 | - etc 57 | 58 | - ExpressionStatement 59 | - expression: CallExpression # outer console.log 60 | - expression: PropertyAccessExpression 61 | - etc 62 | - arguments: CallExpression 63 | - etc 64 | ``` 65 | 66 | [See AST Explorer](https://astexplorer.net/#/gist/80c981c87035a45a753c0ee5c983ecc9/6276351b153f4dac9811bf7214c9b236ae420c7e) 67 | 68 | Each node has a different variable to work with (so you can't just say 69 | `if node.children { node.children.foreach(lookAtNode) }` ) but instead you need to examine each node individually. 70 | 71 | ## Checking a Statement 72 | 73 | Initially the meat of the work starts in [`checkSourceElementWorker`][6] which has a `switch` statement that 74 | contains all legitimate nodes which can start a statement. Each node in the tree then does it's checking. 75 | 76 | Let's try get a really early error, with this bad code: 77 | 78 | ```ts 79 | // A return statement shouldn't exist here in strict mode (or any mode?) 80 | return; 81 | ~~~~~~ 82 | ``` 83 | 84 | It goes into [`checkReturnStatement`][6] which uses [`getContainingFunction`][7] to determine if the `return` 85 | lives inside a [`function-like`][8] node ( e.g. `MethodSignature`, `CallSignature`, `JSDocSignature`, 86 | `ConstructSignature`, `IndexSignature`, `FunctionType`, `JSDocFunctionType`, `ConstructorType`). 87 | 88 | Because the parent of the `return` statement is the root (`parent: SourceFileObject`) then it's going to fail. 89 | This triggers [`grammarErrorOnFirstToken`][9] which will raise the error: 90 | `A 'return' statement can only be used within a function body.ts(1108)` and declare the error underline to the 91 | first token inside that node. 92 | 93 | ## Checking Type Equality 94 | 95 | ```ts 96 | const myString = "Hello World"; 97 | const myInt = 123; 98 | // Error: This condition will always return 'false' since the types '123' and '"Hello World"' have no overlap. 99 | if (myInt === myString) { 100 | // Do something 101 | } 102 | ``` 103 | 104 | To get to this error message: 105 | 106 | - [`checkSourceElementWorker`][6] loops through the 3 statements in the `SourceFile` 107 | - In the third, it goes through: 108 | - [`checkIfStatement`][13] 109 | - [`checkTruthinessExpression`][11] 110 | - [`checkExpression`][12] 111 | - [`checkBinaryLikeExpression`][14] where it fails. 112 | - The fail comes from inside [`isTypeRelatedTo`][15] which has a set of heuristics for whether the types are the 113 | same ([`isSimpleTypeRelatedTo`][16] uses cached data in NodeLinks, and [`checkTypeRelatedTo`][17] dives deeper) 114 | and if not then. It will raise. 115 | 116 | ### Caching Data While Checking 117 | 118 | Note there are two ways in which TypeScript is used, as a server and as a one-off compiler. In a server, we want 119 | to re-use as much as possible between API requests, and so the Node tree is treated as immutable data until there 120 | is a new AST. 121 | 122 | This gets tricky inside the Type Checker, which for speed reasons needs to cache data somewhere. The solution to 123 | this is the [`NodeLinks`][3] property on a Node. The Type Checker fills this up during the run and re-uses it, 124 | then it is discarded next time you re-run the checker. 125 | 126 | ### Type Flags 127 | 128 | Because TypeScript is a [structural type system][20], every type can reasonably be compared with every other type. 129 | One of the main ways in which TypeScript keeps track of the underlying data-model is via the 130 | [`enum TypeFlags`][19]. Accessed via `.flags` on any node, it is a value which is used via bitmasking to let you 131 | know what data it represents. 132 | 133 | If you wanted to check for whether the type is a union: 134 | 135 | ```ts 136 | if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object) { 137 | // is a union object 138 | } 139 | ``` 140 | 141 | When running the compiler in debug mode, you can see a string version of this via `target.__debugFlags`. 142 | 143 | ### Type Comparison 144 | 145 | The entry point for comparing two types happens in [`checkTypeRelatedTo`][17]. This function is mostly about 146 | handling the diagnostic results from any checking though and doesn't do the work. The honour of that goes to 147 | [`isRelatedTo`][18] which: 148 | 149 | - Figures out what the source and target types should be (based on freshness (a literal which was created in an 150 | expression), whether it is substituted () or simplifiable (a type which depends first on resolving another 151 | type)) 152 | 153 | - First, check if they're identical via [`isIdenticalTo`][21]. The check for most objects occurs in 154 | [`recursiveTypeRelatedTo`][22], unions and intersections have a check that compares each value in 155 | [`eachTypeRelatedToSomeType`][23] which eventually calls [`recursiveTypeRelatedTo`][22] for each item in the 156 | type. 157 | 158 | - The heavy lifting of the comparison depending on what flags are set on the node is done in 159 | [`structuredTypeRelatedTo`][23]. Where it picks off one by one different possible combinations for matching and 160 | returns early as soon as possible. 161 | 162 | A lot of the functions related to type checking return a [`Ternary`][24], which an enum with three states, true, 163 | false and maybe. This gives the checker the chance to admit that it probably can't figure out whether a match is 164 | true currently (maybe it hit the 100 depth limit for example) and potentially could be figured out coming in from 165 | a different resolution. 166 | 167 | TODO: what are substituted types? 168 | 169 | ## Debugging Advice 170 | 171 | - If you want to find a diagnostic in the codebase, search for `diag(WXZY` e.g 172 | ``, you'll find `src/compiler/diagnosticInformationMap.generated.ts` has it being referenced by a key. Search 173 | for that key. 174 | 175 | - If you're working from an error and want to see the path it took to get there, you can add a breakpoint in 176 | [`createFileDiagnostic`][10] which should get called for all diagnostic errors. 177 | 178 | 179 | [0]: 180 | [1]: 181 | [2]: 182 | [3]: 183 | [4]: GLOSSARY.md#statements 184 | [ast]: GLOSSARY.md#statements 185 | [5]: 186 | [6]: 187 | [7]: 188 | [8]: 189 | [9]: 190 | [10]: 191 | [11]: 192 | [12]: 193 | [13]: 194 | [14]: 195 | [15]: 196 | [16]: 197 | [17]: 198 | [17]: 199 | [19]: 200 | [20]: GLOSSARY.md#structural-type-system 201 | [21]: 202 | [22]: 203 | [22]: 204 | [23]: 205 | 206 | -------------------------------------------------------------------------------- /codebase/src/compiler/checker-widening-narrowing.md: -------------------------------------------------------------------------------- 1 | # Widening and Narrowing in Typescript 2 | 3 | Typescript has a number of related concepts in which a type gets 4 | treated temporarily as a similar type. Most of these concepts are 5 | internal-only. None of them are documented very well. For the internal 6 | concepts, we expect nobody needs to know about them to use the 7 | language. For the external concepts, we hope that they work well 8 | enough that most people *still* don't need to think about them. This 9 | document explains them all, aiming to help two audiences: (1) advanced 10 | users of Typescript who *do* need to understand the quirks of the 11 | language (2) contributors to the Typescript compiler. 12 | 13 | The concepts covered in this document are as follows: 14 | 15 | 1. Widening: treat an internal type as a normal one. 16 | 2. Literal widening: treat a literal type as a primitive one. 17 | 3. Narrowing: remove constituents from a union type. 18 | 4. Instanceof narrowing: treat a type as a subclass. 19 | 5. Apparent type: treat a non-object type as an object type. 20 | 21 | ## Widening 22 | 23 | Widening is the simplest operation of the bunch. The types `null` and 24 | `undefined` are converted to `any`. This happens 25 | recursively in object types, union types, and array types (including 26 | tuples). 27 | 28 | Why widening? Well, historically, `null` and `undefined` were internal 29 | types that needed to be converted to `any` for downstream consumers 30 | and for display. With `--strictNullChecks`, widening doesn't happen 31 | any more. But without it, widening happens a lot, generally when obtaining 32 | a type from another object. Here are some examples: 33 | 34 | ```ts 35 | // @strict: false 36 | let x = null; 37 | ``` 38 | 39 | Here, `null` has the type `null`, but `x` has the type `any` because 40 | of widening on assignment. `undefined` works the same way. However, 41 | with `--strict`, `null` is preserved, so no widening will happen. 42 | 43 | ## Literal widening 44 | 45 | Literal widening is significantly more complex than "classic" 46 | widening. Basically, when literal widening happens, a literal type 47 | like `"foo"` or `SomeEnum.Member` gets treated as its base type: 48 | `string` or `SomeEnum`, respectively. The places where literals widen, 49 | however, cause the behaviour to be hard to understand. Literal 50 | widening is described fully 51 | [at the literal widening PR](https://github.com/Microsoft/TypeScript/pull/10676) 52 | and 53 | [its followup](https://github.com/Microsoft/TypeScript/pull/11126). 54 | 55 | ### When does literal widening happen? 56 | 57 | There are two key points to understand about literal widening. 58 | 59 | 1. Literal widening only happens to literal types that originate from 60 | expressions. These are called *fresh* literal types. 61 | 2. Literal widening happens whenever a fresh literal type reaches a 62 | "mutable" location. 63 | 64 | For example, 65 | 66 | ```ts 67 | const one = 1; // 'one' has type: 1 68 | let num = 1; // 'num' has type: number 69 | ``` 70 | 71 | Let's break the first line down: 72 | 73 | 1. `1` has the fresh literal type `1`. 74 | 2. `1` is assigned to `const one`, so `one: 1`. But the type `1` is still 75 | fresh! Remember that for later. 76 | 77 | Meanwhile, on the second line: 78 | 79 | 1. `1` has the fresh literal type `1`. 80 | 2. `1` is assigned to `let num`, a mutable location, so `num: number`. 81 | 82 | Here's where it gets confusing. Look at this: 83 | 84 | ```ts 85 | const one = 1; 86 | let wat = one; // 'wat' has type: number 87 | ``` 88 | 89 | The first two steps are the same as the first example. The third step 90 | 91 | 1. `1` has the fresh literal type `1`. 92 | 2. `1` is assigned to `const one`, so `one: 1`. 93 | 3. `one` is assigned to `wat`, a mutable location, so `wat: number`. 94 | 95 | This is pretty confusing! The fresh literal type `1` makes its way 96 | *through* the assignment to `one` down to the assignment to `wat`. But 97 | if you think about it, this is what you want in a real program: 98 | 99 | ```ts 100 | const start = 1001; 101 | const max = 100000; 102 | // many (thousands?) of lines later ... 103 | for (let i = start; i < max; i = i + 1) { 104 | // did I just write a for loop? 105 | // is this a C program? 106 | } 107 | ``` 108 | 109 | If the type of `i` were `1001` then you couldn't write a for loop based 110 | on constants. 111 | 112 | There are other places that widen besides assignment. Basically it's 113 | anywhere that mutation could happen: 114 | 115 | ```ts 116 | const nums = [1, 2, 3]; // 'nums' has type: number[] 117 | nums[0] = 101; // because Javascript arrays are always mutable 118 | 119 | const doom = { e: 1, m: 1 } 120 | doom.e = 2 // Mutable objects! We're doomed! 121 | 122 | // Dooomed! 123 | // Doomed! 124 | // -gasp- Dooooooooooooooooooooooooooooooooo- 125 | ``` 126 | 127 | ### What literal types widen? 128 | 129 | * Number literal types like `1` widen to `number`. 130 | * String literal types like `'hi'` widen to `string`. 131 | * Boolean literal types like `true` widen to `boolean`. 132 | * Enum members widen to their containing enum. 133 | 134 | An example of the last is: 135 | 136 | ```ts 137 | enum State { 138 | Start, 139 | Expression, 140 | Term, 141 | End 142 | } 143 | const start = State.Start; 144 | let state = start; 145 | let ch = ''; 146 | while (ch = nextChar()) { 147 | switch (state) { 148 | // ... imagine your favourite tokeniser here 149 | } 150 | } 151 | ``` 152 | 153 | ## Narrowing 154 | 155 | Narrowing is essentially the removal of types from a union. It's 156 | happening all the time as you write code, especially if you use 157 | `--strictNullChecks`. To understand narrowing, you first need to 158 | understand the difference between "declared type" and "computed type". 159 | 160 | The declared type of a variable is the one it's declared with. For 161 | `let x: number | undefined`, that's `number | undefined`. The computed 162 | type of a variable is the type of the variable as it's used in 163 | context. Here's an example: 164 | 165 | ```ts 166 | // @strict: true 167 | type Thing = { name: 'one' | 'two' }; 168 | function process(origin: Thing, extra?: Thing | undefined): void { 169 | preprocess(origin, extra); 170 | if (extra) { 171 | console.log(extra.name); 172 | if (extra.name === 'one') { 173 | // ... 174 | ``` 175 | 176 | `extra`'s declared type is `Thing | undefined`, since it's an optional 177 | parameter. However, its computed type varies based on context. On the 178 | first line, in `preprocess(origin, extra)`, its computed type is still 179 | `Thing | undefined`. However, inside the `if (extra)` block, `extra`'s 180 | computed type is now just `Thing` because it can't possibly be 181 | `undefined` due to the `if (extra)` check. Narrowing has removed 182 | `undefined` from its type. 183 | 184 | Similarly, the declared type of `extra.name` is `'one' | 'two'`, but 185 | inside the true branch of `if (extra.name === 'one')`, its computed 186 | type is just `'one'`. 187 | 188 | Narrowing mostly commonly removes all but one type from a union, but 189 | doesn't necessarily need to: 190 | 191 | ```ts 192 | type Type = Anonymous | Class | Interface 193 | function f(thing: string | number | boolean | object) { 194 | if (typeof thing === 'string' || typeof thing === 'number') { 195 | return lookup[thing]; 196 | } 197 | else if (typeof thing === 'boolean' && thing) { 198 | return globalCachedThing; 199 | } 200 | else { 201 | return thing; 202 | } 203 | } 204 | ``` 205 | 206 | Here, in the first if-block, `thing` narrows to `string | number` because 207 | the check allows it to be either string or number. 208 | 209 | ## Instanceof Narrowing 210 | 211 | Instanceof narrowing looks similar to normal narrowing, and 212 | behaves similarly, but its rules are somewhat different. It only 213 | applies to certain `instanceof` checks and type predicates. 214 | 215 | Here's a use of `instanceof` that follows the normal narrowing rules: 216 | 217 | ```ts 218 | class C { c: any } 219 | function f(x: C | string) { 220 | if (x instanceof C) { 221 | // x is C here 222 | } 223 | else { 224 | // x is string here 225 | } 226 | } 227 | ``` 228 | 229 | So far this follows the normal narrowing rules. But `instanceof` 230 | applies to subclasses too: 231 | 232 | ```ts 233 | class D extends C { d: any } 234 | function f(x: C) { 235 | if (x instanceof D) { 236 | // x is D here 237 | } 238 | else { 239 | // x is still just C here 240 | } 241 | } 242 | ``` 243 | 244 | Unlike narrowing, `instanceof` narrowing doesn't remove any types to 245 | get `x`'s computed type. It just notices that `D` is a subclass of `C` 246 | and changes the computed type to `D` inside the `if (x instanceof D)` 247 | block. In the `else` block `x` is still `C`. 248 | 249 | If you mess up the class relationship, the compiler does its best 250 | to make sense of things: 251 | 252 | ```ts 253 | class E { e: any } // doesn't extend C! 254 | function f(x: C) { 255 | if (x instanceof E) { 256 | // x is C & E here 257 | } 258 | else { 259 | // x is still just C here 260 | } 261 | } 262 | ``` 263 | 264 | The compiler thinks that something of type `C` can't also be 265 | `instanceof E`, but just in case, it sets the computed type of `x` to 266 | `C & E`, so that you can use the properties of `E` in the block 267 | — just be aware that the block will probably never execute! 268 | 269 | ### Type predicates 270 | 271 | Type predicates follow the same rules as `instanceof` when narrowing, 272 | and are just as subject to misuse. So this example is equivalent to 273 | the previous wonky one: 274 | 275 | ```ts 276 | function isE(e: any): e is E { 277 | return e.e; 278 | } 279 | function f(x: C) { 280 | if (isE(x)) { 281 | // x is C & E here 282 | } 283 | else { 284 | // nope, still just C 285 | } 286 | } 287 | ``` 288 | 289 | ## Apparent Type 290 | 291 | In some situations you need to get the properties on a variable, even 292 | when it technically doesn't have properties. One example is primitives: 293 | 294 | ```ts 295 | let n = 12 296 | let s = n.toFixed() 297 | ``` 298 | 299 | `12` doesn't technically have properties; `Number` does. In order to 300 | map `number` to `Number`, we define `Number` as the *apparent type* of 301 | `number`. Whenever the compiler needs to get properties of some type, 302 | it asks for the apparent type of that type first. This applies to 303 | other non-object types like type parameters: 304 | 305 | ```ts 306 | interface Node { 307 | parent: Node; 308 | pos: number; 309 | kind: number; 310 | } 311 | function setParent(node: T, parent: Node): T { 312 | node.parent = parent; 313 | return node; 314 | } 315 | ``` 316 | 317 | `T` is a type parameter, which is just a placeholder. But its 318 | constraint is `Node`, so when the compiler checks `node.parent`, it 319 | gets the apparent type of `T`, which is `Node`. Then it sees that 320 | `Node` has a `parent` property. 321 | -------------------------------------------------------------------------------- /codebase/src/compiler/binder.md: -------------------------------------------------------------------------------- 1 | # Binder 2 | 3 | The binder walks the tree visiting each declaration in the tree. 4 | For each declaration that it finds, it creates a `Symbol` that records its location and kind of declaration. 5 | Then it stores that symbol in a `SymbolTable` in the containing node, like a function, block or module file, that is the current scope. 6 | `Symbol`s let the checker look up names and then check their declarations to determine types. 7 | It also contains a small summary of what kind of declaration it is -- mainly whether it is a value, a type, or a namespace. 8 | 9 | Since the binder is the first tree walk before checking, it also does some other tasks: setting up the control flow graph, 10 | as well as annotating parts of the tree that will need to be downlevelled for old ES targets. 11 | 12 | Here's an example: 13 | 14 | ```ts 15 | // @Filename: main.ts 16 | var x = 1 17 | console.log(x) 18 | ``` 19 | 20 | The only declaration in this program is `var x`, which is contained in the SourceFile node for `main.ts`. 21 | Functions and classes introduce new scopes, so they are containers -- at the same time as being declarations themselves. So in: 22 | 23 | ```ts 24 | function f(n: number) { 25 | const m = n + 1 26 | return m + n 27 | } 28 | ``` 29 | 30 | The binder ends up with a symbol table for `f` that contains two entries: `n` and `m`. 31 | The binder finds `n` while walking the function's parameter list, and it finds `m` while walking the block that makes up `f`'s body. 32 | 33 | Both `n` and `m` are marked as values. 34 | However, there's no problem with adding another declaration for `n`: 35 | 36 | ```ts 37 | function f(n: number) { 38 | type n = string 39 | const m = n + 1 40 | return m + n 41 | } 42 | ``` 43 | 44 | Now `n` has two declarations, one type and one value. 45 | The binder disallows more than one declaration of a kind of symbols with *block-scoped* declaration. 46 | Examples are `type`, `function`, `class`, `let`, `const` and parameters; *function-scoped* declarations include `var` and `interface`. 47 | But as long as the declarations are of different kinds, they're fine. 48 | 49 | ## Walkthrough 50 | 51 | ```ts 52 | function f(m: number) { 53 | type n = string 54 | const n = m + 1 55 | return m + n 56 | } 57 | ``` 58 | 59 | The binder's basic tree walk starts in `bind`. 60 | There, it first encounters `f` and calls `bindFunctionDeclaration` and then `bindBlockScopeDeclaration` with `SymbolFlags.Function`. 61 | This function has special cases for files and modules, but the default case calls `declareSymbol` to add a symbol in the current container. 62 | There is a lot of special-case code in `declareSymbol`, but the important path is to check whether the symbol table already contains a symbol with the name of the declaration -- `f` in this case. 63 | If not, a new symbol is created. 64 | If so, the old symbol's exclude flags are checked against the new symbol's flags. 65 | If they conflict, the binder issues an error. 66 | 67 | Finally, the new symbol's `flags` are added to the old symbol's `flags` (if any), and the new declaration is added to the symbol's `declarations` array. 68 | In addition, if the new declaration is for a value, it is set as the symbol's `valueDeclaration`. 69 | 70 | ## Containers 71 | 72 | After `declareSymbol` is done, the `bind` visits the children of `f`; `f` is a container, so it calls `bindContainer` before `bindChildren`. 73 | The binder is recursive, so it pushes `f` as the new container by copying it to a local variable before walking its children. 74 | It pops `f` by copying the stored local back into `container`. 75 | 76 | The binder tracks the current lexical container as a pair of variables `container` and `blockScopedContainer` (and `thisParentContainer` if you OOP by mistake). 77 | It's implemented as a global variable managed by the binder walk, which pushes and pops containers as needed. 78 | The container's symbol table is initialised lazily, by `bindBlockScopedDeclaration`, for example. 79 | 80 | ## Flags 81 | 82 | The table for which symbols may merge with each other is complicated, but it's implemented in a surprisingly small space using the bitflag enum `SymbolFlags`. 83 | The downside is that the bitflag system is very confusing. 84 | 85 | The basic rule is that a new declaration's *flags* may not conflict with the *excludes flags* of any previous declarations. 86 | Each kind of declaration has its own exclude flags; each one is a list of declaration kinds that cannot merge with that declaration. 87 | 88 | In the example above, `type n` is a type alias, which has flags = `SymbolFlags.TypeAlias` and excludeFlags = `SymbolFlags.TypeAliasExcludes`. 89 | The latter is an alias of `SymbolFlags.Type`, meaning generally that type aliases can't merge with anything that declares a type: 90 | 91 | ```ts 92 | Type = Class | Interface | Enum | EnumMember | TypeLiteral | TypeParameter | TypeAlias 93 | ``` 94 | Notice that this list includes `TypeAlias` itself, and declarations like classes and enums that also declare values. 95 | `Value` includes `Class` and `Enum` as well. 96 | 97 | Next, when the binder reaches `const n`, it uses the flag `BlockScopedVariable` and excludeFlags `BlockScopedVariableExcludes`. 98 | `BlockScopedVariableExcludes = Value`, which is a list of every kind of value declaration. 99 | 100 | ```ts 101 | Value = Variable | Property | EnumMember | ObjectLiteral | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor 102 | ``` 103 | 104 | `declareSymbol` looks up the existing excludeFlags for `n` and makes sure that `BlockScopedVariable` doesn't conflict; `BlockScopedVariable & Type === 0` so it doesn't. 105 | Then it *or*s the new and old flags and the new and old excludeFlags. 106 | In this example, that will prevent more value declarations because `BlockScopedVariable & (Value | Type) !== 0`. 107 | 108 | Here's some half-baked example code which shows off what you'd write if SymbolFlags used string enums and sets instead of bitflags. 109 | 110 | ```ts 111 | const existing = symbolTable.get(name) 112 | const flags = SymbolFlags[declaration.kind] // eg "Function" 113 | if (existing.excludes.has(flags)) { 114 | error("Cannot redeclare", name) 115 | } 116 | existing.flags.add(flags) 117 | for (const ex of ExcludeFlags[declaration.kind]) { 118 | existing.excludeFlags.add(ex) 119 | } 120 | ``` 121 | 122 | ## Cross-file global merges 123 | 124 | Because the binder only binds one file at a time, the above system for merges only works with single files. 125 | For global (aka script) files, declarations can merge across files. 126 | This happens in the checker in `initializeTypeChecker`, using `mergeSymbolTable`. 127 | 128 | ## Special names 129 | 130 | In `declareSymbol`, `getDeclarationName` translates certain nodes into internal names. 131 | `export=`, for example, gets translated to `InternalSymbolName.ExportEquals` 132 | 133 | Elsewhere in the binder, function expressions without names get `"__function"` 134 | Computed property names that aren't literals get `"__computed"`, manually. 135 | 136 | TODO: Finish this 137 | 138 | ## Control Flow 139 | 140 | TODO: Missing completely 141 | 142 | ## Emit flags 143 | 144 | TODO: Missing completely 145 | 146 | ## Exports 147 | 148 | TODO: Missing completely 149 | 150 | ## Javascript and CommonJS 151 | 152 | Javascript has additional types of declarations that it recognises, which fall into 3 main categories: 153 | 154 | 1. Constructor functions and pre-class-field classes: assignments to `this.x` properties. 155 | 2. CommonJS: assignments to `module.exports`. 156 | 3. Global browser code: assignments to namespace-like object literals. 157 | 4. JSDoc declarations: tags like `@type` and `@callback`. 158 | 159 | Four! Four main categories! 160 | 161 | The first three categories really aren't much different from Typescript declarations. 162 | The main complication is that not all assignments are declarations, so there's quite a bit of code that decides which assignments should be treated as declarations. 163 | The checker is fairly resilient to non-declaration assignments being included, so it's OK if the code isn't perfect. 164 | 165 | In `bindWorker`'s `BinaryExpression` case, `getAssignmentDeclarationKind` is used to decide whether an assignment matches the syntactic requirements for declarations. 166 | Then each kind of assignment dispatches to a different binding function. 167 | 168 | ### Global namespace creation code 169 | 170 | In addition to CommonJS, JS also supports creating global namespaces by assignments of object literals, functions and classes to global variables. 171 | This code is very complicated and is *probably* only ever used by Closure code bases, so it might be possible to remove it someday. 172 | 173 | ``` js 174 | var Namespace = {} 175 | Namespace.Mod1 = {} 176 | Namespace.Mod2 = function () { 177 | // callable module! 178 | } 179 | Namespace.Mod2.Sub1 = { 180 | // actual contents 181 | } 182 | ``` 183 | 184 | TODO: This is unfinished. 185 | 186 | ### JSDoc declarations 187 | 188 | TODO: This is unfinished. 189 | 190 | ### Conflicting object literal export assignments 191 | 192 | One particuarly complex case of CommonJS binding occurs when there is an object literal export assignment in the same module as `module.exports` assignments: 193 | 194 | ```js 195 | module.exports = { 196 | foo: function() { return 1 }, 197 | bar: function() { return 'bar' }, 198 | baz: 12, 199 | } 200 | if (isWindows) { 201 | // override 'foo' with Windows-specific version 202 | module.exports.foo = function () { return 11 } 203 | } 204 | ``` 205 | 206 | In this case, the desired exports of the file are `foo, bar, baz`. 207 | Even though `foo` is declared twice, it should have one export with two declarations. 208 | The type should be `() => number`, though that's the responsibility of the checker. 209 | 210 | In fact, this structure is too complicated to build in the binder, so the checker produces it through merges, using the same merge infrastructure it uses for cross-file global merges. 211 | The binder treats this pretty straightforwardly; it calls `bindModuleExportsAssignment` for `module.exports = {...`, which creates a single `export=` export. 212 | Then it calls `bindExportsPropertyAssignment` for `module.exports.foo = ...`, which creates a `foo` export. 213 | 214 | Having `export=` with other exports is impossible with ES module syntax, so the checker detects it and copies all the top-level exports into the `export=`. 215 | In the checker, `resolveExternalModuleSymbol` returns either an entire module's exports, or all the exports in an `export=`. 216 | In the combined CommonJS case we're discussing, `getCommonJsExportEquals` also checks whether a module has exports *and* `export=`. 217 | If it does, it copies each of the top-level exports into the `export=`. 218 | If a property with the same name already exists in the `export=`, the two are merged with `mergeSymbol`. 219 | 220 | Subsequent code in the checker that doesn't use `resolveExternalModuleSymbol` (is there any?) has to ignore the `export=`, since its contents are now just part of the module. 221 | -------------------------------------------------------------------------------- /codebase/src/compiler/checker-inference.md: -------------------------------------------------------------------------------- 1 | # Type Inference 2 | 3 | TypeScript has a number of related techniques which together are 4 | called type inference: places where a type is discovered from 5 | inspecting values instead of a type annotation. This document 6 | covers them all in one place even though they're all fairly different. 7 | 8 | One thing that that is true of all type inference in TypeScript: 9 | type inference is a separate step that happens before checking. The 10 | checker will infer a type for a location; then it will check the type 11 | in the normal way, as if the type had been explicitly written. This 12 | results in redundant checking when the type inference is simple. 13 | 14 | None of these techniques are Hindley-Milner type inference. Instead, 15 | TypeScript adds a few ad-hoc inference techniques to its normal 16 | type-checking. The result is a system that can infer from many useful 17 | locations, but nowhere near all of them. 18 | 19 | ## Initialiser inference 20 | 21 | The simplest kind of inference is from initialisers. This inference is 22 | so simple that I don't believe it has been given a separate name until 23 | now. 24 | 25 | You can see this anywhere a variable, parameter or property has an 26 | initialiser: 27 | 28 | ```ts 29 | let x = 123 30 | function f(x = 123) { 31 | } 32 | class C { 33 | x = 123 34 | } 35 | ``` 36 | 37 | Remember, inference precedes checking, so checking `let x = 123` 38 | looks like this: 39 | 40 | 1. Look for the type of `x`. 41 | 2. There is no annotation, so use the (widened) type of the initialiser: `number`. 42 | 3. Check that the initialiser's type `123` is assignable to `number`. 43 | 44 | ## Contextual typing 45 | 46 | Contextual typing looks upward in the tree for a type based on a type 47 | annotation. This is unlike initialiser inference, which looks at a *sibling* 48 | node for a type based on a *value*. For example, in 49 | 50 | ```ts 51 | const f: Callback = (a,b) => a.length + b 52 | ``` 53 | 54 | The parameters `a` and `b` are contextually typed by the type 55 | `Callback`. The checker discovers this by looking at the parent nodes 56 | of `a` and `b` until it finds a type annotation on a variable declaration. 57 | 58 | In fact, contextual typing only applies to two kinds of things: 59 | parameters and literals (including JSX literals). But it may find a type in a variety of places. 60 | Here are 3 typical ones: 61 | 62 | 1. A type annotation on a declaration: 63 | 64 | ```ts 65 | type Config = { before(data: string): void } 66 | const cfg: Config = { 67 | before(x) { 68 | console.log(x.length) 69 | } 70 | } 71 | ``` 72 | 73 | 2. The left-hand side of an assignment: 74 | 75 | ```ts 76 | let steps: ('up' | 'down' | 'left' | 'right')[] = ['up', 'up', 'down', 'down'] 77 | steps = ['down'] 78 | ``` 79 | 80 | 3. An argument in a function call: 81 | 82 | ```ts 83 | declare function setup(register: (name: string, age: number) => void): void 84 | setup((name, age) => console.log(name, age)) 85 | ``` 86 | 87 | The basic mechanism of contextual typing is a search for a type 88 | annotation. Once a type annotation is found, contextual typing walks 89 | down through the *type* by reversing the path it walked up through the 90 | *tree*. 91 | 92 | Aside: In example (2), contextual typing gives `'down'` the 93 | *non-widening* type `'down'`; it would otherwise have the type 94 | `string`. That means `['down']` will have the type `'down'[]`, which 95 | is assignable to `steps`. So contextual typing lets programmers avoid 96 | writing `['down' as 'down']` in some cases. 97 | 98 | ### Walkthrough 99 | 100 | Let's walk through example (1). 101 | 102 | 1. During normal check of the tree, 103 | `checkFunctionExpressionOrObjectLiteralMethod` is called on 104 | `before`. 105 | 2. This calls `getApparentTypeofContextualType` (after a few 106 | intermediate functions), which 107 | recursively looks for the contextual type of `before`'s parent. 108 | 3. The parent is an object literal, which recursively looks for the 109 | contextual type of the object literal's parent. 110 | 4. The parent is a variable declaration with a type annotation `Config`. 111 | This is the contextual type of the object literal. 112 | 5. Next we look inside `Config` for a property named `before`. Since's 113 | `Config.before`'s type is a signature, that signature is the 114 | contextual type of `before`. 115 | 6. Finally, `assignContextualParameterTypes` assigns a type for `x` from 116 | `Config.before`'s first parameter. 117 | 118 | Note that if you have type annotations on some parameters already, 119 | `assignContextualParameterTypes` will skip those parameters. 120 | 121 | Contextually typing `(name, age) => ...` in (3) works substantially 122 | that same. When the search reaches `getContextualType`, instead of a 123 | variable declaration, the parent is a call expression. The contextual 124 | type of a call expression is the type of the callee, `setup` in this 125 | case. Now, as before, we look inside `setup`'s type: `(name, age) => 126 | ...` is the first argument, so its contextual type is from the first 127 | parameter of `setup`, `register`. `assignmentContextualParameterTypes` 128 | works for `name` and `age` as in (1). 129 | 130 | ## Type Parameter Inference 131 | 132 | Type parameter inference is quite different from the other two 133 | techniques. It still infers **types** based on provided **values**, 134 | but the inferred types don't replace a type annotation. Instead 135 | they're provided as type arguments to a function, which results in 136 | instantiating a generic function with some specific type. For example: 137 | 138 | ```ts 139 | declare function setup(config: { initial(): T }): T 140 | setup({ initial() { return "last" } }) 141 | ``` 142 | 143 | First checks `{ initial() { return "last" } }` to get `{ initial(): 144 | string }`. By matching `T` in `{ initial(): T }` with `string` in `{ 145 | initial(): string }`, it infers that `T` is `string`, making the 146 | second line the same as if the author had written: 147 | 148 | ```ts 149 | setup({ initial() { return "last" } }) 150 | ``` 151 | 152 | Meaning that the compiler then checks that 153 | `{ initial() { return "last" } }` is assignable to 154 | `{ initial(): string }`. 155 | 156 | ### Walkthrough 157 | 158 | Type parameter inference starts off in `inferTypeArguments`, where 159 | the first step in type parameter inference is to get the type of all 160 | the arguments to the function whose parameters are being inferred. In 161 | the above example, the checker says that the type of 162 | `{ initial() { return "last" } }` is `{ initial(): string }`. This 163 | type is called the **source** type, since it is the source of 164 | inferences. It's matched with the parameter type `{ initial(): T }`. 165 | This is the **target** type -- it contains type parameters which are 166 | the target of the process. 167 | 168 | Type parameter inference is a pairwise walk of the two types, looking 169 | for type parameters in the target, matching them to corresponding 170 | types in the source. The type is walked structurally sort of like a tree 171 | is elsewhere in the compiler. 172 | 173 | 1. `inferTypes` gets called on each source/target pair with 174 | argument=source/parameter=target. There's only one pair here: 175 | `{ initial(): string }` and `{ initial(): T }`. 176 | 2. Since both sides are object types, `inferFromProperties` looks 177 | through each property of the target and looks for a match in the 178 | source. In this case both have the property `initial`. 179 | 3. `initial`'s type is a signature on both sides 180 | (`() => T/() => string`), so inference goes to `inferFromSignature`, which 181 | recursively infers from the return type. 182 | 4. Now the source/target pair is `T/string`. Since the source is a 183 | lone type parameter, we add `string` to the list of candidates for 184 | `T`. 185 | 186 | Once all the parameters have had `inferTypes` called on them, 187 | `getInferredTypes` condenses each candidate array to a single type, 188 | via `getUnionType` in this case. `T`'s candidates array is `[string]`, 189 | so `getUnionType` immediately returns `string`. 190 | 191 | ### Other considerations 192 | 193 | #### Method of Combining Candidate Arrays 194 | 195 | Only inference to return types, `keyof T` and mapped type constraints 196 | (which are usually `keyof` too) produce a union. These are all 197 | contravariant inference locations. All other locations 198 | call the custom code `getCommonSupertype`, which more or less does 199 | what it says. Note that object types are always unioned together 200 | first, regardless of inference position. 201 | 202 | #### Interference Between Contextual Typing and Type Parameter Inference 203 | 204 | Type parameter inference actually operates in two passes. The first 205 | pass skips arguments that have contextually typed expressions so that 206 | if good inferences are found from other arguments, contextual typing 207 | can provide types to parameters of function expressions, which in turn 208 | may produce better return types. Then the second pass proceeds with 209 | all arguments. 210 | 211 | #### Inference Priorities 212 | 213 | Different positions have different inference priorities; when the type 214 | walk finds a candidate at a higher priority position than existing 215 | candidates, it throws away the existing candidates and starts over 216 | with the higher-priority candidate. For example, a lone type variable 217 | has the highest priority, but a type variable found inside a return type 218 | has one of the lowest priorities. 219 | 220 | Priorities have two important limitations: 221 | first, they are defined ad-hoc, based on heuristics developed by 222 | observing bad type inferences and trying to fix them. Second, throwing away 223 | low-priority inferences is faster, but will miss some inferences 224 | compared to integrating all priorities in some way. 225 | 226 | #### Contravariant Candidates 227 | 228 | Certain candidates are inferred contravariantly, such as parameters of 229 | callbacks. This is a separate system from inference priorities; 230 | contravariant candidates are even higher priority. 231 | 232 | #### Reverse Mapped Types 233 | 234 | A reverse mapped type is a mapped type that is constructed during 235 | inference, and it requires information obtained from inference, but is 236 | not a central part of inference. A reverse mapped type is constructed when 237 | the target is a mapped type and the source is an object type. It 238 | allows a inference to apply to every member of an object type: 239 | 240 | ```ts 241 | type Box = { ref: T } 242 | type Boxed = { [K in keyof T]: Box } 243 | declare function unbox(boxed: Boxed): T; 244 | unbox({ a: { ref: 1 }, m: { ref: "1" } }) // returns { a: number, m: string } 245 | ``` 246 | 247 | Reverse mapped types are normal types just like conditional types, 248 | index types, mapped types, etc. The difference is that they have no 249 | explicit syntax to construct them. 250 | 251 | 252 | 253 | [0]: 254 | [1]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html#strict-function-types 255 | 256 | 257 | -------------------------------------------------------------------------------- /GLOSSARY.md: -------------------------------------------------------------------------------- 1 | ### Terminology from inside the codebase 2 | 3 | - `Parser` - Takes source code and tries to convert it into an in-memory AST representation which you can work 4 | with in the compiler. Also: [see Parser](https://basarat.gitbooks.io/typescript/docs/compiler/parser.html) 5 | - `Scanner` - Used by the parser to convert a string an chops into tokens in a linear fashion, then it's up to a 6 | parser to tree-ify them. Also: [see Scanner](https://basarat.gitbooks.io/typescript/docs/compiler/scanner.html) 7 | - `Binder` - Creates a symbol map and uses the AST to provide the type system 8 | [See Binder](https://basarat.gitbooks.io/typescript/docs/compiler/binder.html) 9 | - `Checker` - Takes the AST, symbols and does the type checking and inference - 10 | [See Checker](https://basarat.gitbooks.io/typescript/docs/compiler/checker.html) 11 | - `Token` - A set of characters with some kind of semantic meaning, a parser generates a set of tokens 12 | - `AST` - An abstract syntax tree. Basically the in-memory representation of all the identifiers as a tree of 13 | tokens. 14 | - `Node` - An object that lives inside the tree 15 | - `Location` / `Range` 16 | - `Freshness` - When a literal type is first created and not expanded by hitting a mutable location, see [Widening 17 | and Narrowing in TypeScript][wnn]. 18 | - `Symbol` - An object that tracks all the places a variable or type is declared 19 | - `Transient Symbol` - A symbol created in the checker, as opposed to in the binder 20 | 21 | ### Type stuff which can be see outside the compilers 22 | 23 | - `Expando` - This is [the term](https://developer.mozilla.org/en-US/docs/Glossary/Expando) used to describe taking a JS object and adding new things to it which expands the type's shape 24 | 25 | ```js 26 | function doSomething() {} 27 | doSomething.doSomethingElse = () => {} 28 | ``` 29 | 30 | In TS, this is only allowed for adding properties to functions. In JS, this is a normal pattern in old school code for all kinds of objects. TypeScript will augment the types for `doSomething` to add `doSomethingElse` in the type system in both. 31 | 32 | 33 | - `Structural Type System` - A school of types system where the way types are compared is via the structure of 34 | their properties. 35 | 36 | For example: 37 | 38 | ```ts 39 | interface Duck { 40 | hasBeak: boolean; 41 | flap: () => void; 42 | } 43 | 44 | interface Bird { 45 | hasBeak: boolean; 46 | flap: () => void; 47 | } 48 | ``` 49 | 50 | These two are the exact same inside TypeScript. The basic rule for TypeScript’s structural type system is that 51 | `x` is compatible with `y` if `y` has at least the same members as `x`. 52 | 53 | * `Literal` - A literal type is a type that only has a single value, e.g. `true`, `1`, `"abc"`, `undefined`. 54 | 55 | For immutable objects, TypeScript creates a literal type which is the value. For mutable objects TypeScript 56 | uses the general type that the literal matches. See [#10676](https://github.com/Microsoft/TypeScript/pull/10676) 57 | for a longer explanation. 58 | 59 | ```ts 60 | // The types are the literal: 61 | const c1 = 1; // Type 1 62 | const c2 = c1; // Type 1 63 | const c3 = "abc"; // Type "abc" 64 | const c4 = true; // Type true 65 | const c5 = c4 ? 1 : "abc"; // Type 1 | "abc" 66 | 67 | // The types are the class of the literal, because let allows it to change 68 | let v1 = 1; // Type number 69 | let v2 = c2; // Type number 70 | let v3 = c3; // Type string 71 | let v4 = c4; // Type boolean 72 | let v5 = c5; // Type number | string 73 | ``` 74 | 75 | Literal types are sometimes called unit types, because they have only one ("unit") value. 76 | 77 | - `Control Flow Analysis` - using the natural branching and execution path of code to change the types at 78 | different locations in your source code by static analysis. 79 | 80 | ```ts 81 | type Bird = { color: string, flaps: true }; 82 | type Tiger = { color: string, stripes: true }; 83 | declare animal: Bird | Tiger 84 | 85 | if ("stripes" in animal) { 86 | // Inside here animal is only a tiger, because TS could figure out that 87 | // the only way you could get here is when animal is a tiger and not a bird 88 | } 89 | ``` 90 | 91 | - `Generics` - A way to have variables inside a type system. 92 | 93 | ```ts 94 | function first(array: any[]): any { 95 | return array[0]; 96 | } 97 | ``` 98 | 99 | You want to be able to pass a variable type into this function, so you annotate the function with angle brackets 100 | and a _type parameter_: 101 | 102 | ```ts 103 | function first(array: T[]): T { 104 | return array[0]; 105 | } 106 | ``` 107 | 108 | This means the return type of `first` is the same as the type of the array elements passed in. (These can start 109 | looking very complicated over time, but the principle is the same; it just looks more complicated because of the 110 | single letter.) Generic functions should always use their type parameters in more than one position (e.g. above, 111 | `T` is used both in the type of the `array` parameter and in the function’s return type). This is the heart of 112 | what makes generics useful—they can specify a _relationship_ between two types (e.g., a function’s output is the 113 | same as input, or a function’s two inputs are the same type). If a generic only uses its type parameter once, it 114 | doesn’t actually need to be generic at all, and indeed some linters will warn that it’s a _useless generic_. 115 | 116 | Type parameters can usually be inferred from function arguments when calling generics: 117 | 118 | ```ts 119 | first([1, 2, 3]); // 'T' is inferred as 'number' 120 | ``` 121 | 122 | It’s also possible to specify them explicitly, but it’s preferable to let inference work when possible: 123 | 124 | ```ts 125 | first(["a", "b", "c"]); 126 | ``` 127 | 128 | * `Outer type parameter` - A type parameter declared in a parent generic construct: 129 | 130 | ```ts 131 | class Parent { 132 | method(x: T, y: U): U { 133 | // 'T' is an *outer* type parameter of 'method' 134 | // 'U' is a *local* type parameter of 'method' 135 | } 136 | } 137 | ``` 138 | 139 | * `Narrowing` - Taking a union of types and reducing it to fewer options. 140 | 141 | A great case is when using `--strictNullCheck` when using control flow analysis 142 | 143 | ```ts 144 | // I have a dog here, or I don't 145 | declare const myDog: Dog | undefined; 146 | 147 | // Outside the if, myDog = Dog | undefined 148 | if (dog) { 149 | // Inside the if, myDog = Dog 150 | // because the type union was narrowed via the if statement 151 | dog.bark(); 152 | } 153 | ``` 154 | 155 | - `Expanding` - The opposite of narrowing, taking a type and converting it to have more potential values. 156 | 157 | ```ts 158 | const helloWorld = "Hello World"; // Type; "Hello World" 159 | 160 | let onboardingMessage = helloWorld; // Type: string 161 | ``` 162 | 163 | When the `helloWorld` constant was re-used in a mutable variable `onboardingMessage` the type which was set is an 164 | expanded version of `"Hello World"` which went from one value ever, to any known string. 165 | 166 | - `Transient` - a symbol created in the checker. 167 | 168 | The checker creates its own symbols for unusual declarations: 169 | 170 | 1. Cross-file declarations 171 | 172 | ```ts 173 | // @filename: one.ts 174 | interface I { a } 175 | // @filename: two.ts 176 | interface I { b } 177 | ``` 178 | 179 | The binder creates two symbols for I, one in each file. Then the checker creates a merged symbol that has both declarations. 180 | 181 | 2. Synthetic properties 182 | 183 | ```ts 184 | type Nats = Record<'one' | 'two', number> 185 | ``` 186 | 187 | The binder doesn't create any symbols for `one` or `two`, because those properties don't exist until the Record mapped type creates them. 188 | 189 | 3. Complex JS patterns 190 | 191 | ```js 192 | var f = function g() { 193 | g.expando1 = {} 194 | 195 | } 196 | f.expando2 = {} 197 | ``` 198 | 199 | People can put expando properties on a function expression inside or outside the function, using different names. 200 | - `Partial Type` - 201 | - `Synthetic` - a property that doesn't have a declaration in source. 202 | - `Union Types` 203 | - `Enum` 204 | - `Discriminant` 205 | - `Intersection` 206 | - `Indexed Type` - A way to access subsets of your existing types. 207 | 208 | ```ts 209 | interface User { 210 | profile: { 211 | name: string; 212 | email: string; 213 | bio: string; 214 | }; 215 | account: { 216 | id: string; 217 | signedUpForMailingList: boolean; 218 | }; 219 | } 220 | 221 | type UserProfile = User["profile"]; // { name: string, email: string, bio: string } 222 | type UserAccount = User["account"]; // { id: string, signedUpForMailingList: string } 223 | ``` 224 | 225 | This makes it easier to keep a single source of truth in your types. 226 | 227 | - `Index Signatures` - A way to tell TypeScript that you might not know the keys, but you know the type of values 228 | of an object. 229 | 230 | ```ts 231 | interface MySettings { 232 | [index: string]: boolean; 233 | } 234 | 235 | declare function getSettings(): MySettings; 236 | const settings = getSettings(); 237 | const shouldAutoRotate = settings.allowRotation; // boolean 238 | ``` 239 | 240 | - `IndexedAccess` - ( https://github.com/Microsoft/TypeScript/pull/30769 ) 241 | - `Conditional Types` 242 | - `Contextual Types` 243 | - `Substitution` 244 | - `NonPrimitive` 245 | - `Instantiable` 246 | - `Tuple` - A mathematical term for a finite ordered list. Like an array but with a known length. 247 | 248 | TypeScript lets you use these as convenient containers with known types. 249 | 250 | ```ts 251 | // Any item has to say what it is, and whether it is done 252 | type TodoListItem = [string, boolean]; 253 | 254 | const chores: TodoListItem[] = [["read a book", true], ["done dishes", true], ["take the dog out", false]]; 255 | ``` 256 | 257 | Yes, you could use an object for each item in this example, but tuples are there when it fits your needs. 258 | 259 | - `Mapped Type` - A type which works by taking an existing type and creating a new version with modifications. 260 | 261 | ```ts 262 | type Readonly = { readonly [P in keyof T]: T[P] }; 263 | 264 | // Map for every key in T to be a readonly version of it 265 | // e.g. 266 | interface Dog { 267 | furColor: string; 268 | hasCollar: boolean; 269 | } 270 | 271 | // Using this 272 | type ReadOnlyDog = Readonly; 273 | 274 | // Would be 275 | interface ReadonlyDog { 276 | readonly furColor: string; 277 | readonly hasCollar: boolean; 278 | } 279 | ``` 280 | 281 | This can work where you 282 | 283 | - `Type Assertion` - override its inferred and analyzed view of a type 284 | 285 | ```ts 286 | interface Foo { 287 | bar: number; 288 | bas: string; 289 | } 290 | var foo = {} as Foo; 291 | foo.bar = 123; 292 | foo.bas = "hello"; 293 | ``` 294 | 295 | - `Incremental Parsing` - Having an editor pass a range of edits, and using that range to invalidate a cache of 296 | the AST. Re-running the type checker will keep the out of range nodes and only parse the new section. 297 | - `Incremental Compiling` - The compiler keeps track of all compilation hashes, and timestamps when a file has 298 | been transpiled. Then when a new module is changed, it will use the import/export dependency graph to invalidate 299 | and re-compile only the affected code. 300 | 301 | ### Rarely heard 302 | 303 | - `Deferred` 304 | - `Homomorphic` 305 | 306 | ### JS Internals Specifics 307 | 308 | [`Statement`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements) - "JavaScript 309 | applications consist of statements with an appropriate syntax. A single statement may span multiple lines. 310 | Multiple statements may occur on a single line if each statement is separated by a semicolon. This isn't a 311 | keyword, but a group of keywords." 312 | 313 | [wnn]: https://github.com/sandersn/manual/blob/master/Widening-and-Narrowing-in-Typescript.md 314 | -------------------------------------------------------------------------------- /intro/README.md: -------------------------------------------------------------------------------- 1 | ## The TypeScript Compiler 2 | 3 | There's a lot to learn, and this document is going to try and give you a very high level overview of how the 4 | TypeScript compiler works and how you can contribute to it. 5 | 6 | Here are the key sections in this docs: 7 | 8 | - [Getting Set up](#getting-set-up) 9 | - [What is in the TypeScript repo?](#what-is-in-the-typescript-repo) 10 | - [The TypeScript Compiler](#the-typescript-compiler) 11 | - [The TSServer](#the-tsserver) 12 | - [Tips and Tricks](#tips-and-tricks) 13 | 14 | ## Getting Set Up 15 | 16 | You need a modern version [of Node.js](https://nodejs.org/en/) installed and then: 17 | 18 | - Clone the repo: `git clone https://github.com/microsoft/TypeScript` 19 | - `cd TypeScript` 20 | - `npm i` - installs dependencies 21 | - `npm run gulp` - builds the TypeScript compiler 22 | - `code .` then briefly come back to terminal to run: 23 | - `npm test` 24 | 25 | With that done, you should have a copy of the compiler up and running with tests running in the background to 26 | prove that everything is working correctly. This could take around 10m, you should see be a progress indicator in 27 | your terminal. 28 | 29 | ### What happened during setup 30 | 31 | TypeScript is built in TypeScript. The compiler uses a "last known good" version of the TypeScript to bootstrap, 32 | you can find this version in `/lib`. The command `npm run gulp` created a copy of the compiler at `built/local`. 33 | The corresponding version of the `tsc` compiler used in the npm package is now available via: 34 | `node built/local/tsc.js --help`. 35 | 36 | TypeScript has a _comprehensive_ test suite, most of the tests are high level integration tests which you can find 37 | at `tests/cases`. Given the scope and depth of a compiler like TypeScript, we use a lot of tests to ensure 38 | improvements don't break old requirements. 39 | 40 | ### Configuring your editor 41 | 42 | To get your VS Code environment working smoothly, set up the per-user config: 43 | 44 | - Set up your `.vscode/launch.json` by running: `cp .vscode/launch.template.json .vscode/launch.json` 45 | - Set up your `.vscode/settings.json` by running: `cp .vscode/settings.template.json .vscode/settings.json` 46 | 47 | ## What is in the TypeScript repo? 48 | 49 | The TypeScript repo is a single codebase which creates a set of files which are released as the "typescript" npm 50 | module: 51 | 52 | - `typescript.js` - The compiler API 53 | - `tsc.js` - The command line experience for using TypeScript, which uses `typescript.js` 54 | - `typescriptServices.js` - A web server which responds to questions about TS/JS code from editors, which uses 55 | `typescript.js` 56 | - `tsserver.js` - The command line wrapper for `typescriptServices.js` 57 | 58 | You can consider there to be three main entry points into TypeScript: Directly via 59 | `import * as ts from "typescript"`, the CLI and tsserver. 60 | 61 | ![./imgs/layers-2.png](./imgs/layers-2.png) 62 | 63 | We'll be concentrating on an overview of these two parts: The TypeScript compiler and tsserver 64 | 65 | ### The TypeScript Compiler 66 | 67 | The goal of the TypeScript compiler is to take a collection of `*.ts`/`*.js`/`*.json`/`*.d.ts` source files, 68 | _optionally_ run a type checker over those files, and emit corresponding `*.js`/`*.d.ts`/`*.js.map` files. 69 | 70 | Abstractly, you can think of the compiler as going through this linear process (in reality it gets a little more 71 | complicated, but as an overview it's fine) to achieve its goal: 72 | 73 | ![./imgs/compiler-linear.png](./imgs/compiler-linear.png) 74 | 75 | Let's go through these systems at a high level, indicating what sort of bugs or features might touch them. 76 | 77 | #### Parsing Text 78 | 79 | The Parser is an object which controls a Scanner. The Scanner iterates (mostly) forwards through the source code's 80 | text, dividing it into a sequence of words and punctuation. 81 | 82 | For example: 83 | 84 | ```ts 85 | function hello() { 86 | console.log("Hi"); 87 | } 88 | ``` 89 | 90 | Produces a scanner result of: 91 | `FunctionKeyword, WhitespaceTrivia, Identifier, OpenParenToken, CloseParenToken, WhitespaceTrivia, OpenBraceToken, NewLineTrivia, WhitespaceTrivia, Identifier, DotToken, Identifier, OpenParenToken, StringLiteral, CloseParenToken, SemicolonToken, NewLineTrivia, CloseBraceToken, EndOfFileToken`. 92 | You can use [this playground plugin](https://www.typescriptlang.org/play?install-plugin=playground-ts-scanner) to 93 | see the tokens from a scanner for any TS/JS code. 94 | 95 | The parser will take that set of syntax tokens and form a syntax tree which is a series of nested nodes starting 96 | at a `SourceFile`: 97 | 98 | ``` 99 | SourceFile: 100 | - statements: [ 101 | Function Declaration 102 | - name: Identifier 103 | - body: Block 104 | statements: [ 105 | ExpressionStatement 106 | expression: CallExpress 107 | ... 108 | ] 109 | ] 110 | ``` 111 | 112 | This is the TypeScript syntax tree, and it is one of the core data models in the compiler as it represents the 113 | structure of the code in memory. You can explore the TypeScript syntax tree in 114 | [the Playground](https://www.typescriptlang.org/play/#code/GYVwdgxgLglg9mABACwKYBt1wBQEpEDeAUIohAgM5zqoB0WA5tgEQASMzuA3EQL5A) 115 | (by turning on the "AST" setting) or in 116 | [TypeScript AST Viewer](https://ts-ast-viewer.com/#code/GYVwdgxgLglg9mABACwKYBt1wBQEpEDeAUIohAgM5zqoB0WA5tgEQASMzuA3EQL5A). 117 | 118 | Common changes to the parser are ones that add TypeScript syntax that does not exist in JavaScript, or adds new JavaScript syntax 119 | that was approved by the TC39 committee. 120 | 121 | #### Type Checking 122 | 123 | TypeScript's type system works by creating symbols when "something" is declared in a scope. That "something" could 124 | be a variable, type, interface, function or more. A symbol has a set of flags that indicate what kind(s) of 125 | declaration(s) it's for. Symbols are then compared to each other during assignment and are used throughout the type system for 126 | checking. 127 | 128 | For example, this code: 129 | 130 | ```ts 131 | function hello() { 132 | console.log("Hi"); 133 | } 134 | 135 | hello; 136 | ``` 137 | 138 | creates one new symbol `hello` whose declaration is the function `hello`. The following flag is set: 139 | `SymbolFlags.Function`. The compiler looks up this symbol whenever the name `hello` appears, like on the last line of the code. 140 | You can use [this playground plugin](https://www.typescriptlang.org/play?install-plugin=playground-ts-symbols) to see the 141 | symbols generated for any TS/JS code. 142 | 143 | Creating symbols is the responsibility of the binder, which is the first part of the type checking process. 144 | 145 | Type checking in TypeScript happens in the file `checker.ts`, this is a 40k line function which does a huge amount 146 | of work. The checker works primarily by recursively diving into, known as "walking", the syntax tree, and making assertions about the 147 | nodes on its way through. 148 | 149 | As a rough outline, using the code above, the following functions in the checker would be called: 150 | 151 | ``` 152 | checkSourceFileWorker (for the root SourceFile) 153 | - checkSourceElementWorker (for each Statement) 154 | - checkFunctionDeclaration (for the Function Declaration) 155 | - checkBlock (for the function's body Block) 156 | ... 157 | ``` 158 | 159 | Effectively each syntax node has its own type checking function, and that's usually a good place to start if you 160 | want to add or amend a diagnostic which TypeScript raises. 161 | 162 | Common changes to the type checker are PRs which revise, fix or create new error diagnostics around code patterns. 163 | 164 | #### Emit 165 | 166 | The code in `emitter.ts` creates output `.js`, `.d.ts`, `.map` files from `.js` or `.ts` files. 167 | 168 | The emitter first uses a series of transformers to take the input file's syntax tree and convert that into a new 169 | syntax tree which fits the constraints of the tsconfig. For example if your target was `es2015` then the 170 | transformers for Class Fields, ESNext, ES2020, ES2019, ES2018, ES2017, ES2016, ES2015, and Generators would run. 171 | You can use 172 | [this playground plugin](https://www.typescriptlang.org/play?install-plugin=playground-transformer-timeline) to 173 | see the individual transformations for any TS/JS code. 174 | 175 | After the transforms are finished, the emitter recurses through the new syntax tree printing out nodes into a 176 | new text file. 177 | 178 | Common changes to the emitter are PRs which transform new TypeScript syntax, or new JavaScript features from TC39, 179 | into JavaScript that works on older platforms. 180 | 181 | ## TSServer 182 | 183 | The TSServer is responsible for providing information to text editors. The TSServer powers features like code 184 | completion, refactoring tools and jump to definition. The TSServer is similar to the language server protocol 185 | but is older. 186 | 187 | ## Tips and Tricks 188 | 189 | ### Start with 'backlog', 'good first issues' or 'help wanted' issues 190 | 191 | There are thousands of issues on the TypeScript repo, and we triage them all. The issues which we have already 192 | confirmed mean there is less need to have discussion ahead of time. 193 | 194 | - [Good first issues](https://github.com/microsoft/TypeScript/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) 195 | - [Help wanted](https://github.com/microsoft/TypeScript/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) 196 | - [Backlog](https://github.com/microsoft/TypeScript/milestone/29) 197 | 198 | ### Look for similar merged PRs 199 | 200 | If your goal is to fix or amend an existing system, then it's quite likely that similar code has been created, 201 | there have already been 10k merged PRs to TypeScript. Finding a similar PR can really help narrow down where your 202 | existing change lives and how it can be tested. 203 | 204 | ### Starting with a diagnostic message 205 | 206 | If you have an existing error message, and are looking to work around that area. All _english_ messages are stored 207 | in `src/compiler/diagnosticInformationMap.generated.ts`, first find the message you are working from, then search 208 | the codebase for the key in that file. E.g: 209 | 210 | - Search `diagnosticInformationMap.generated.ts` for "Convert invalid character to its html entity code" 211 | - Find the line 212 | `Convert_invalid_character_to_its_html_entity_code: diag(95100, DiagnosticCategory.Message, "Convert_invalid_character_to_its_html_entity_code_95100", "Convert invalid character to its html entity code")` 213 | - Search the codebase for `Convert_invalid_character_to_its_html_entity_code` to find its usage 214 | 215 | ### Embrace the debugger 216 | 217 | To test it out the debugger, open up `src/compiler/checker.ts` find 218 | `function checkSourceFileWorker(node: SourceFile) {` and add a `debugger` statement on the first line in the 219 | function. 220 | 221 | ```diff 222 | function checkSourceFileWorker(node: SourceFile) { 223 | + debugger 224 | const links = getNodeLinks(node); 225 | ``` 226 | 227 | Breakpoints will work too. 228 | 229 | If you open up `tests/cases/fourslash/getDeclarationDiagnostics.ts` and then run your new debugging launch task 230 | `Mocha Tests (currently opened test)`. VS Code will switch into the debugging mode and hit the debugger statement 231 | in your TypeScript. 232 | 233 | You'll probably want to add the following to your watch section in VS Code: 234 | 235 | - `node.__debugKind` 236 | - `node.__debugGetText()` 237 | - `source.symbol.declarations[0].__debugKind` 238 | - `target.symbol.declarations[0].__debugKind` 239 | 240 | This is really useful for keeping track of changing state, and it's pretty often that those are the names of 241 | things you're looking for. 242 | 243 | ### Testing your changes 244 | 245 | There are three main testing systems in the compiler: 246 | 247 | - **Baselines**: Baseline tests are file-based snapshot tests, each test verifies the compiler's output based on 248 | an input file. You can use comments starting with @ to set compiler flags in that test: `// @target: es5`. 249 | - **Fourslash**: Fourslash tests are unit tests which set up a virtual file system in any comment with four 250 | slashes `////`. Then test has a custom set of functions which interact with that virtual file system like a text 251 | editor would. 252 | - **Unit tests**: These are traditional mocha unit tests 253 | 254 | You run tests via `gulp runtests`. 255 | 256 | Flags worth knowing: 257 | 258 | - `--failed` - re-runs the failed tests only 259 | - `--no-lint` - don't run the linter when it completes 260 | - `-i` - Use the inspector to debug 261 | 262 | If you have a change to the in the baselines: 263 | 264 | - `gulp diff` - to see the differences 265 | - `gulp baseline-accept` to overwrite the current baseline snapshots 266 | --------------------------------------------------------------------------------