├── presentation ├── index.js ├── .gitignore ├── slides │ ├── 0_title.md │ ├── 10_agenda.md │ ├── 99_thank_you.md │ ├── 30_summary.md │ └── 20_main.md ├── assets │ └── ogp.png ├── .fusumarc.yml ├── package.json ├── styles.css └── gulpfile.js ├── ls-web-local ├── .gitignore ├── src │ ├── consts.ts │ ├── util.ts │ ├── components │ │ ├── Editor.tsx │ │ ├── App.tsx │ │ ├── Errors.tsx │ │ └── Logger.tsx │ ├── types.ts │ ├── index.tsx │ ├── logger.ts │ ├── contexts │ │ ├── LscContext.tsx │ │ └── ConfigureContext.tsx │ ├── lang-service │ │ ├── lib-defitions.ts │ │ ├── svc.ts │ │ ├── project.ts │ │ ├── script-info.ts │ │ └── lang-service.ts │ ├── index.html │ ├── editor │ │ └── index.ts │ └── lang-service-client │ │ └── index.ts ├── package.json ├── webpack.config.js ├── .yo-rc.json └── tsconfig.json ├── sample-projects └── symple │ ├── .gitignore │ ├── test_tsserver.sh │ ├── package.json │ ├── yarn.lock │ └── tsconfig.json ├── ls-sample ├── package.json ├── src │ └── parse-performance.ts ├── tsconfig.json └── yarn.lock ├── README.md ├── LICENSE └── .gitignore /presentation/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ls-web-local/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | built/ 3 | -------------------------------------------------------------------------------- /presentation/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | fusuma_wd/ 3 | .figmatoken 4 | -------------------------------------------------------------------------------- /sample-projects/symple/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | sample.ts 3 | *.log 4 | -------------------------------------------------------------------------------- /ls-web-local/src/consts.ts: -------------------------------------------------------------------------------- 1 | export const TS_LIB_FILE_PREFIX = "tslib://"; 2 | -------------------------------------------------------------------------------- /presentation/slides/0_title.md: -------------------------------------------------------------------------------- 1 | # **TypeScript Server Side Anatomy** 2 | 3 | ### TypeScript meetup #1 4 | -------------------------------------------------------------------------------- /presentation/slides/10_agenda.md: -------------------------------------------------------------------------------- 1 | ## About me 2 | 3 | - Yosuke Kurami (@Quramy) 4 | - Web front-end developer 5 | -------------------------------------------------------------------------------- /presentation/assets/ogp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quramy/ts-server-side-anatomy/HEAD/presentation/assets/ogp.png -------------------------------------------------------------------------------- /presentation/slides/99_thank_you.md: -------------------------------------------------------------------------------- 1 | # Thank you! 2 | 3 | [*https://github.com/Quramy/tsjp-resources*](https://github.com/Quramy/tsjp-resources) 4 | -------------------------------------------------------------------------------- /presentation/slides/30_summary.md: -------------------------------------------------------------------------------- 1 | ## **Summray** 2 | 3 | --- 4 | 5 | ### TypeScript server implements: 6 | 7 | * Editor agnostic protocol 8 | * High-performance mutation 9 | * Script file updating 10 | * Incremental parsing 11 | 12 | --- 13 | 14 | # **tsserver is awesome** 😎 15 | -------------------------------------------------------------------------------- /sample-projects/symple/test_tsserver.sh: -------------------------------------------------------------------------------- 1 | cat << EOF > sample.ts 2 | function hoge(x: string) { 3 | console.log(x); 4 | } 5 | EOF 6 | 7 | npx tsserver << EOF | tail -n 1 | jq .body 8 | {"command":"open","arguments":{"file":"${PWD}/sample.ts"}} 9 | {"command":"quickinfo","arguments":{"file":"${PWD}/sample.ts","line":2,"offset":15}} 10 | EOF 11 | -------------------------------------------------------------------------------- /sample-projects/symple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symple", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "typescript": "^3.5.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ls-web-local/src/util.ts: -------------------------------------------------------------------------------- 1 | import { Location } from "./types"; 2 | 3 | export function pos2loc(input: { row: number, column: number }) { 4 | return { 5 | line: input.row + 1, 6 | offset: input.column + 1, 7 | } as Location; 8 | } 9 | 10 | export function loc2pos(input: Location) { 11 | return { 12 | row: input.line - 1, 13 | column: input.offset - 1, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /sample-projects/symple/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | typescript@^3.5.1: 6 | version "3.5.1" 7 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" 8 | integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== 9 | -------------------------------------------------------------------------------- /ls-web-local/src/components/Editor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { setupEditor } from "../editor"; 3 | import { lscContext } from "../contexts/LscContext"; 4 | import { conigCtx } from "../contexts/ConfigureContext"; 5 | 6 | export const Editor = () => { 7 | const lsp = useContext(lscContext); 8 | return ( 9 |
setupEditor(ref, lsp)} /> 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /ls-web-local/src/types.ts: -------------------------------------------------------------------------------- 1 | import { Ace } from "ace-builds"; 2 | import { Observable } from "rxjs"; 3 | 4 | export type Location = { 5 | line: number, 6 | offset: number, 7 | }; 8 | 9 | export type StreamType = S extends Observable ? T : never; 10 | 11 | export type Optional = {[P in keyof T]?: T[P]}; 12 | 13 | export type Configuration = { 14 | initialContent: string, 15 | debounceTime: number, 16 | useComplete: boolean, 17 | showLogger: boolean, 18 | }; 19 | -------------------------------------------------------------------------------- /ls-sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ls-sample", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@types/react": "^16.8.19", 14 | "typescript": "^3.5.1" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^12.0.3", 18 | "ts-node-dev": "^1.0.0-pre.39" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /presentation/.fusumarc.yml: -------------------------------------------------------------------------------- 1 | meta: 2 | url: https://quramy.github.io/ts-server-side-anatomy/ 3 | name: TypeScript Server Side Anatomy 4 | author: Quramy 5 | thumbnail: https://quramy.github.io/ts-server-side-anatomy/assets/ogp.png 6 | description: Explain what TypeScript server(tsserver) does under your editors 7 | sns: 8 | - twitter 9 | slide: 10 | loop: false 11 | sidebar: true 12 | targetBlank: true 13 | showIndex: false 14 | isVertical: false 15 | code: 16 | languages: 17 | - bash 18 | - javascript 19 | - typescript 20 | plugins: [] 21 | theme: default 22 | extends: 23 | js: index.js 24 | css: styles.css 25 | -------------------------------------------------------------------------------- /ls-web-local/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "react-dom"; 3 | import { App } from "./components/App"; 4 | 5 | import { LscProvider } from "./contexts/LscContext"; 6 | import { rootLogger } from "./logger"; 7 | import { ConfigProvider, conigCtx } from "./contexts/ConfigureContext"; 8 | 9 | const elm = document.getElementById("app"); 10 | 11 | rootLogger.getStream().subscribe(x => console.log(x)); 12 | 13 | if (elm) { 14 | const { Consumer } = conigCtx; 15 | render(( 16 | 17 | 18 | {({ initial }) => } 19 | 20 | 21 | ), elm); 22 | } 23 | -------------------------------------------------------------------------------- /presentation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "presentation", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "fusuma start", 8 | "prebuild": "rimraf dist && gulp", 9 | "build": "fusuma build -d fusuma_wd && mv fusuma_wd/dist dist", 10 | "postbuild": "gulp postBuild", 11 | "deploy": "gh-pages -d dist" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "fusuma": "^1.1.2", 18 | "gh-pages": "^2.0.1", 19 | "gulp": "^4.0.2", 20 | "gulp-replace": "^1.0.0", 21 | "image-downloader": "^3.4.2", 22 | "request": "^2.88.0", 23 | "request-promise": "^4.2.4", 24 | "rimraf": "^2.6.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ls-web-local/src/logger.ts: -------------------------------------------------------------------------------- 1 | import { ReplaySubject } from "rxjs"; 2 | 3 | export type LoggerArguments = { 4 | id: string, 5 | category: string, 6 | eventName: string, 7 | args: any[], 8 | }; 9 | 10 | let version = 0; 11 | 12 | const subject = new ReplaySubject(200); 13 | 14 | export const rootLogger = { 15 | log(category: string, eventName: string, ...args: any[]) { 16 | subject.next({ id: ++version + "", category, eventName, args }); 17 | }, 18 | getCategory(category: string) { 19 | return { 20 | log(eventName: string, ...args: any[]) { 21 | subject.next({ id: ++version + "", category, eventName, args }); 22 | } 23 | }; 24 | }, 25 | getStream() { 26 | return subject.asObservable(); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /ls-web-local/src/contexts/LscContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, createContext } from "react"; 2 | import { Configuration } from "../types"; 3 | import { LsClient } from "../lang-service-client"; 4 | 5 | const ctx = createContext(null as any); 6 | const { Provider } = ctx; 7 | 8 | export type Props = { 9 | config: Configuration, 10 | }; 11 | 12 | export class LscProvider extends React.Component { 13 | private lsc = new LsClient({ 14 | initialContents: [{ fileName: "/main.ts", content: this.props.config.initialContent }], 15 | debounceTime: this.props.config.debounceTime, 16 | useComplete: this.props.config.useComplete, 17 | }); 18 | 19 | render() { 20 | return ( 21 | 22 | {this.props.children} 23 | 24 | ) 25 | } 26 | } 27 | 28 | export const lscContext = ctx; 29 | -------------------------------------------------------------------------------- /ls-web-local/src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { setupEditor } from "../editor"; 3 | import SplitPane from "react-split-pane"; 4 | import { Errors } from "./Errors"; 5 | import { Logger } from "./Logger"; 6 | import { Editor } from "./Editor"; 7 | import { conigCtx } from "../contexts/ConfigureContext"; 8 | 9 | export const App = () => { 10 | const config = useContext(conigCtx); 11 | const { showLogger } = config.initial; 12 | if (showLogger) { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } else { 23 | return ( 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript Server Side Anatomy 2 | 3 | I've created this repo to explain **what TypeScript server(tsserver) does under your editor**. 4 | 5 | - How editor/IDEs communicate with tsserver 6 | - File structure(Script Version Cache) in language service host 7 | - How TS's incremental parser works 8 | - etc... 9 | 10 | This repository, created for tsjp meetup #1, includes the following: 11 | 12 | - Presentation slides 13 | - Web editor application using TS language service 14 | - Test script to measure TS's incremental parser performance 15 | 16 | ## Presentation slides 17 | 18 | [https://quramy.github.io/ts-server-side-anatomy](https://quramy.github.io/ts-server-side-anatomy) 19 | 20 | 21 | ## Web editor application using TS language service 22 | 23 | See [ls-web-local](ls-web-local) dir. 24 | 25 | And you can check live demonstration out [here](https://quramy.github.io/ts-server-side-anatomy/assets/editor/dist/index.html?no-delay=true). 26 | 27 | ## Figures 28 | 29 | See [Figma](https://www.figma.com/file/DhwRUPAASvvdlFcM0BlRYhE3/ts-meetup-images). 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yosuke Kurami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /ls-web-local/src/lang-service/lib-defitions.ts: -------------------------------------------------------------------------------- 1 | import { TS_LIB_FILE_PREFIX } from "../consts"; 2 | 3 | const rawFileMap = new Map(); 4 | 5 | rawFileMap.set("/lib.d.ts", require("typescript/lib/lib.d.ts").default); 6 | rawFileMap.set("/lib.es5.d.ts", require("typescript/lib/lib.es5.d.ts").default); 7 | rawFileMap.set("/lib.esnext.d.ts", require("typescript/lib/lib.esnext.d.ts").default); 8 | rawFileMap.set("/lib.esnext.full.d.ts", require("typescript/lib/lib.esnext.full.d.ts").default); 9 | rawFileMap.set("/lib.dom.d.ts", require("typescript/lib/lib.dom.d.ts").default); 10 | rawFileMap.set("/lib.webworker.importscripts.d.ts", require("typescript/lib/lib.webworker.importscripts.d.ts").default); 11 | rawFileMap.set("/lib.scripthost.d.ts", require("typescript/lib/lib.scripthost.d.ts").default); 12 | 13 | export function getTypeScriptDefinitionText(name: string) { 14 | if (!name.startsWith(TS_LIB_FILE_PREFIX)) { 15 | throw new Error("invalid prefix: " + name); 16 | } 17 | const libname = name.slice(TS_LIB_FILE_PREFIX.length); 18 | const content = rawFileMap.get(libname); 19 | if (!content) { 20 | throw new Error("not included lib.d.ts: " + libname); 21 | } 22 | return content; 23 | }; 24 | -------------------------------------------------------------------------------- /ls-web-local/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ls-web-local", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server", 8 | "prebuild": "rimraf dist", 9 | "build": "webpack --mode production", 10 | "postbuild": "rimraf ../presentation/assets/editor && mkdir -p ../presentation/assets/editor && cp -rf dist ../presentation/assets/editor", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "file-loader": "^3.0.1", 18 | "html-webpack-plugin": "^3.2.0", 19 | "raw-loader": "^2.0.0", 20 | "rimraf": "^2.6.3", 21 | "ts-loader": "^6.0.2", 22 | "uglifyjs-webpack-plugin": "^2.1.3", 23 | "webpack": "^4.32.2", 24 | "webpack-cli": "^3.3.2", 25 | "webpack-dev-server": "^3.4.1" 26 | }, 27 | "dependencies": { 28 | "@types/node": "^12.0.4", 29 | "@types/react": "^16.8.19", 30 | "@types/react-dom": "^16.8.4", 31 | "ace-builds": "^1.4.4", 32 | "emotion": "^10.0.9", 33 | "react": "^16.8.6", 34 | "react-dom": "^16.8.6", 35 | "react-json-view": "^1.19.1", 36 | "react-split-pane": "^0.1.87", 37 | "rxjs": "^6.5.2", 38 | "typescript": "^3.5.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ls-web-local/src/lang-service/svc.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript/lib/tsserverlibrary"; 2 | import { Location } from "../types"; 3 | 4 | interface AbsolutePositionAndLineText { 5 | absolutePosition: number; 6 | lineText: string | undefined; 7 | } 8 | 9 | export declare class ScriptVersionCache { 10 | private changes; 11 | private readonly versions; 12 | private minVersion; 13 | private currentVersion; 14 | private static readonly changeNumberThreshold; 15 | private static readonly changeLengthThreshold; 16 | private static readonly maxVersions; 17 | private versionToIndex; 18 | private currentVersionToIndex; 19 | edit(pos: number, deleteLen: number, insertedText?: string): void; 20 | getSnapshot(): ts.IScriptSnapshot; 21 | private _getSnapshot; 22 | getSnapshotVersion(): number; 23 | getAbsolutePositionAndLineText(oneBasedLine: number): AbsolutePositionAndLineText; 24 | lineOffsetToPosition(line: number, column: number): number; 25 | positionToLineOffset(position: number): Location; 26 | lineToTextSpan(line: number): ts.TextSpan; 27 | getTextChangesBetweenVersions(oldVersion: number, newVersion: number): ts.TextChangeRange | undefined; 28 | getLineCount(): number; 29 | static fromString(script: string): ScriptVersionCache; 30 | } 31 | 32 | export function createSvcFromString(script: string) { 33 | return (ts.server as any)["ScriptVersionCache"].fromString(script) as ScriptVersionCache; 34 | } 35 | -------------------------------------------------------------------------------- /ls-web-local/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 59 | 60 | 61 |
62 | 63 | 64 | -------------------------------------------------------------------------------- /ls-web-local/src/contexts/ConfigureContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext } from "react"; 2 | import { BehaviorSubject } from "rxjs"; 3 | 4 | import { Configuration, Optional } from "../types"; 5 | import { Observable } from "rxjs"; 6 | 7 | export type ConfigHolder = { 8 | initial: Configuration, 9 | stream: Observable, 10 | } 11 | 12 | export const conigCtx = createContext(null as any); 13 | 14 | const content = `const num1 = 1; 15 | const num2 = 1; 16 | 17 | function sum(x: number, y: number) { 18 | return x + y; 19 | } 20 | 21 | console.log(sum(num1, ));`; 22 | 23 | const { Provider } = conigCtx; 24 | 25 | export class ConfigProvider extends React.Component { 26 | 27 | configHolder: ConfigHolder; 28 | 29 | constructor(props: { }) { 30 | super(props); 31 | const q = new URLSearchParams(location.search); 32 | 33 | const initalConfig: Configuration = { 34 | showLogger: q.get("disabled-logger") === "true" ? false : true, 35 | useComplete: q.get("disabled-completion") === "true" ? false : true, 36 | debounceTime: q.get("no-delay") === "true" ? 100: 1500, 37 | initialContent: content, 38 | }; 39 | 40 | this.configHolder = { 41 | initial: initalConfig, 42 | stream: new BehaviorSubject(initalConfig), 43 | }; 44 | } 45 | 46 | render() { 47 | return ( 48 | 49 | {this.props.children} 50 | 51 | ) 52 | } 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /presentation/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --gutter-large: 50px; 3 | --fig-scale: .5; 4 | } 5 | 6 | body { 7 | font-size: 32px; 8 | } 9 | 10 | @media screen and (max-width: 750px) { 11 | :root { 12 | --gutter-large: 20px; 13 | --fig-scale: .18; 14 | } 15 | 16 | body { 17 | font-size: 18px; 18 | } 19 | 20 | pre[class*='language-'], 21 | code[class*='language-'] { 22 | font-size: 12px; 23 | } 24 | 25 | .chart_big { 26 | display: none; 27 | } 28 | 29 | img.figma_fig { 30 | margin: var(--gutter-large) auto; 31 | } 32 | } 33 | @media screen and (min-width: 751px) { 34 | .chart_small { 35 | display: none; 36 | } 37 | } 38 | 39 | section:not(.current) iframe { 40 | display: none; 41 | } 42 | 43 | .editorFrame { 44 | width: 100%; 45 | height: 80vh; 46 | } 47 | 48 | iframe:not(.editorFrame) { 49 | margin: 20px auto; 50 | } 51 | 52 | section.slide > h2 + *:not([id]) { 53 | margin-top: var(--gutter-large); 54 | } 55 | 56 | ul:not(:first-child) { 57 | margin-top: var(--gutter-large); 58 | } 59 | 60 | ul:not(:last-child) { 61 | margin-top: var(--gutter-large); 62 | } 63 | 64 | li > ul:not(:first-child) { 65 | margin-top: 0; 66 | } 67 | 68 | ul>li { 69 | line-height: 1.6; 70 | } 71 | 72 | blockquote { 73 | margin-top: var(--gutter-large); 74 | } 75 | 76 | blockquote p { 77 | font-size: 3.8rem; 78 | line-height: 5rem; 79 | font-style: italic; 80 | } 81 | 82 | img.figma_fig { 83 | zoom: var(--fig-scale); 84 | } 85 | 86 | li, p { 87 | line-height: 4rem; 88 | } 89 | -------------------------------------------------------------------------------- /ls-web-local/src/components/Errors.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from "react"; 2 | import { lscContext } from "../contexts/LscContext"; 3 | import { StreamType } from "../types"; 4 | import { css } from "emotion"; 5 | 6 | const styles = { 7 | root: css` 8 | background-color: #2f3129; 9 | color: red; 10 | min-height: 50px; 11 | height: 100%; 12 | font-size: 1.2rem; 13 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 14 | `, 15 | list: css` 16 | padding: 8px 50px; 17 | `, 18 | item: css` 19 | &:not(:first-child) { 20 | margin-top: 4px; 21 | } 22 | `, 23 | col: css` 24 | margin-right: 8px; 25 | `, 26 | }; 27 | 28 | export type Props = { 29 | fileName: string, 30 | } 31 | export const Errors = ({ fileName }: Props) => { 32 | const lspClient = useContext(lscContext); 33 | const errors$ = lspClient.getErrors$(fileName); 34 | const [errors, updateErrors] = useState>([]); 35 | 36 | useEffect(() => errors$.subscribe(updateErrors).unsubscribe, [lspClient]); 37 | 38 | return ( 39 |
40 |
    41 | {errors.map(error => ( 42 |
  • 43 | {error.row + 1}:{error.column + 1} 44 | [TS{error.code}] 45 | {error.text} 46 |
  • 47 | ))} 48 |
49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /ls-web-local/src/components/Logger.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from "react"; 2 | import ReactJson from 'react-json-view' 3 | import { rootLogger, LoggerArguments } from "../logger"; 4 | import { css } from "emotion"; 5 | 6 | const styles = { 7 | root: css` 8 | background-color: #ddd; 9 | overflow-y: scroll; 10 | height: 100%; 11 | list-style: none; 12 | font-size: 1.3rem; 13 | `, 14 | item: css` 15 | display: flex; 16 | align-items: flex-start; 17 | border-bottom: 1px solid #aaa; 18 | padding: 2px 32px 2px 10px; 19 | &:not(:first-of-type) { 20 | margin-top: 6px; 21 | } 22 | `, 23 | header: css` 24 | color: #20204e; 25 | font-family: monospace; 26 | margin-right: 5px; 27 | `, 28 | obj: css` 29 | flex: auto; 30 | `, 31 | }; 32 | 33 | export const Logger = () => { 34 | const [id, updateId] = useState(""); 35 | const [logs] = useState([]); 36 | const bottomRef = useRef(null); 37 | useEffect(() => { 38 | return rootLogger.getStream().subscribe(x => { 39 | logs.push(x); 40 | updateId(x.id); 41 | setTimeout(() => { 42 | if (bottomRef && bottomRef.current) { 43 | bottomRef.current.scrollIntoView(); 44 | } 45 | }, 100); 46 | }).unsubscribe; 47 | }, [rootLogger]); 48 | return ( 49 |
    50 | {logs.map(({ id, category, eventName, args })=> ( 51 |
  • 52 |
    [{category}:{eventName}]
    53 | {args[0] ?
    54 | 55 |
    : null} 56 |
  • 57 | ))} 58 |
  • 59 |
60 | ); 61 | }; 62 | -------------------------------------------------------------------------------- /ls-sample/src/parse-performance.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript"; 2 | 3 | const insertText = "\ndeclare namespace hoge { export function fooobar(): void; }" 4 | 5 | const rangeItems = (n: number) => { 6 | const arr = []; 7 | for(let i = 0; i < n; i++) { 8 | arr[i] = i; 9 | } 10 | return arr; 11 | }; 12 | 13 | function checkPerf(lines: number) { 14 | const originalSourceText = `declare namespace fuga { 15 | ${rangeItems(lines - 3).map((_, i) => " function fn" + i + "(): number;").join("\n")} 16 | } 17 | `; 18 | 19 | const origSource = ts.createSourceFile("index.d.ts", originalSourceText, ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS); 20 | 21 | const s1 = Date.now(); 22 | let sourceFile = origSource; 23 | for (let i = 0; i < insertText.length; i++) { 24 | const pos = sourceFile.end; 25 | sourceFile = ts.updateSourceFile(sourceFile, sourceFile.getText() + insertText[i], { newLength: 1, span: { start: pos, length: 0 } }); 26 | } 27 | const e1 = Date.now(); 28 | 29 | const s2 = Date.now(); 30 | for (let i = 0; i < insertText.length; i++) { 31 | const origSource = ts.createSourceFile("index.d.ts", originalSourceText + insertText.slice(0, i), ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS); 32 | } 33 | const e2 = Date.now(); 34 | 35 | console.log("==================================") 36 | console.log(`originalSourceText lines: ${originalSourceText.split("\n").length}`); 37 | console.log(`originalSourceText length: ${originalSourceText.length}`); 38 | console.log(`insertText length: ${insertText.length}`); 39 | console.log(`elapsed time with incremental parsing: ${e1 - s1}`); 40 | console.log(`elapsed time with non-incremental parsing: ${e2 - s2}`); 41 | console.log(""); 42 | } 43 | 44 | checkPerf(100); 45 | checkPerf(500); 46 | checkPerf(1000); 47 | checkPerf(5000); 48 | checkPerf(10000); 49 | -------------------------------------------------------------------------------- /ls-web-local/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | /* 6 | * SplitChunksPlugin is enabled by default and replaced 7 | * deprecated CommonsChunkPlugin. It automatically identifies modules which 8 | * should be splitted of chunk by heuristics using module duplication count and 9 | * module category (i. e. node_modules). And splits the chunks… 10 | * 11 | * It is safe to remove "splitChunks" from the generated configuration 12 | * and was added as an educational example. 13 | * 14 | * https://webpack.js.org/plugins/split-chunks-plugin/ 15 | * 16 | */ 17 | 18 | /* 19 | * We've enabled UglifyJSPlugin for you! This minifies your app 20 | * in order to load faster and run less javascript. 21 | * 22 | * https://github.com/webpack-contrib/uglifyjs-webpack-plugin 23 | * 24 | */ 25 | 26 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 27 | 28 | module.exports = { 29 | resolve: { 30 | extensions: [".tsx", ".ts", ".js"], 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | include: [path.resolve(__dirname, 'src')], 36 | loader: 'ts-loader', 37 | test: /\.tsx?$/, 38 | }, 39 | { 40 | include: [path.resolve(__dirname, 'node_modules/typescript/lib')], 41 | loader: 'raw-loader', 42 | test: /lib.*\.d\.ts$/, 43 | }, 44 | ], 45 | }, 46 | 47 | output: { 48 | chunkFilename: '[name].[chunkhash].js', 49 | filename: '[name].[chunkhash].js' 50 | }, 51 | 52 | mode: 'development', 53 | 54 | plugins: [ 55 | new HtmlWebpackPlugin({ 56 | template: "./src/index.html", 57 | }), 58 | ], 59 | 60 | optimization: { 61 | splitChunks: { 62 | cacheGroups: { 63 | vendors: { 64 | priority: -10, 65 | test: /[\\/]node_modules[\\/]/ 66 | } 67 | }, 68 | 69 | chunks: 'async', 70 | minChunks: 1, 71 | minSize: 30000, 72 | name: true 73 | } 74 | }, 75 | devServer: { 76 | contentBase: path.join(__dirname, 'dist'), 77 | compress: true, 78 | port: 9000 79 | }, 80 | }; 81 | -------------------------------------------------------------------------------- /presentation/gulpfile.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | const { parse } = require('url'); 4 | const querystring = require('querystring'); 5 | 6 | const gulp = require("gulp"); 7 | const replace = require("gulp-replace"); 8 | const rp = require("request-promise"); 9 | const imgDownload = require("image-downloader"); 10 | 11 | const token = fs.readFileSync(".figmatoken", "utf-8").trim(); 12 | 13 | let images = []; 14 | 15 | function copyAssets() { 16 | return gulp.src(["assets/**/*"]).pipe(gulp.dest("dist/assets")); 17 | } 18 | 19 | function copy() { 20 | return gulp.src([".fusumarc.yml", "index.js", "styles.css"]).pipe(gulp.dest("fusuma_wd")); 21 | } 22 | 23 | function replaceIframe() { 24 | images = []; 25 | return gulp.src("slides/**/*.md") 26 | .pipe(replace(/ 28 | 29 | [*Architectural Overview*](https://github.com/microsoft/TypeScript/wiki/Architectural-Overview#layer-overview) 30 | 31 | --- 32 | 33 | # **1. Editor agnostic** 34 | 35 | --- 36 | 37 | ## Functions what we want for editor/IDEs 38 | 39 | - Error checking 40 | - Completion 41 | - Jump to definition 42 | - Jump to references 43 | - etc... 44 | 45 | --- 46 | 47 | ## It's toooo tough to implement the features as each editor's plugin 😇 48 | 49 | --- 50 | 51 | ## No more [editor war](https://en.wikipedia.org/wiki/Editor_war)! 52 | 53 | ![editor_war](https://pbs.twimg.com/media/Bhfbnn3CMAA93mg.png:large) 54 | 55 | --- 56 | 57 | ## tsserver 58 | 59 | 60 | 61 | TypeScript's server, a.k.a. **tsserver**, gives language functions to editor/IDEs 👍 62 | 63 | --- 64 | 65 | ## Features of tsserver 66 | 67 | * Communicates over STDIO 68 | * Editor/IDEs need **no JavaScript** code 69 | * Using JSON paylod such as JSON RPC protocol 70 | 71 | --- 72 | 73 | ## Seeing is believing 74 | 75 | ```ts 76 | function hoge(x: string) { 77 | console.log(x); 78 | } 79 | ``` 80 | 81 | Let's ask to tsserver "What's type of `x` at the line: 2 / col: 15" 82 | 83 | --- 84 | 85 | ```bash 86 | # test.sh 87 | 88 | cat << EOF > sample.ts 89 | function hoge(x: string) { 90 | console.log(x); 91 | } 92 | EOF 93 | 94 | npx tsserver << EOF | tail -n 1 | jq .body 95 | {"command":"open","arguments":{"file":"${PWD}/sample.ts"}} 96 | {"command":"quickinfo","arguments":{"file":"${PWD}/sample.ts","line":2,"offset":15}} 97 | EOF 98 | ``` 99 | 100 | ```bash 101 | $ sh test.sh 102 | { 103 | "kind": "parameter", 104 | "kindModifiers": "", 105 | "start": { 106 | "line": 2, 107 | "offset": 15 108 | }, 109 | "end": { 110 | "line": 2, 111 | "offset": 16 112 | }, 113 | "displayString": "(parameter) x: string", 114 | "documentation": "", 115 | "tags": [] 116 | } 117 | ``` 118 | 119 | --- 120 | 121 | ## Another, real world example 122 | 123 | ```bash 124 | $ export TSS_LOG="-file `pwd`/tsserver.log -level verbose" 125 | $ code . 126 | ``` 127 | 128 | --- 129 | 130 | ```txt 131 | Info 0 [3:43:45.342] Starting TS Server 132 | Info 1 [3:43:45.342] Version: 3.5.1 133 | Info 2 [3:43:45.342] Arguments: /Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper.app/Contents/MacOS/Code Helper /tsjp-resources/sample-projects/symple/node_modules/typescript/lib/tsserver.js --useInferredProjectPerProjectRoot --enableTelemetry --cancellationPipeName /var/folders/bc/t99h94ls1k9c59bcjtkx3tmr0000gn/T/vscode-typescript/tscancellation-df93638a114cbddaa780.tmp* --locale en --noGetErrOnBackgroundUpdate --validateDefaultNpmLocation 134 | Info 3 [3:43:45.342] Platform: darwin NodeVersion: 10 CaseSensitive: false 135 | Info 4 [3:43:45.347] Binding... 136 | Info 5 [3:43:45.353] request: 137 | {"seq":0,"type":"request","command":"configure","arguments":{"hostInfo":"vscode","preferences":{"providePrefixAndSuffixTextForRename":true,"allowRenameOfImportPath":true}}} 138 | Info 6 [3:43:45.354] Host information vscode 139 | Info 7 [3:43:45.354] response: 140 | {"seq":0,"type":"response","command":"configure","request_seq":0,"success":true} 141 | Perf 8 [3:43:45.356] 0::configure: async elapsed time (in milliseconds) 2.6602 142 | Info 9 [3:43:45.356] request: 143 | {"seq":1,"type":"request","command":"compilerOptionsForInferredProjects","arguments":{"options":{"module":"commonjs","target":"es2016","jsx":"preserve","allowJs":true,"allowSyntheticDefaultImports":true,"allowNonTsExtensions":true}}} 144 | Info 10 [3:43:45.359] Scheduled: *ensureProjectForOpenFiles* 145 | Perf 11 [3:43:45.359] 1::compilerOptionsForInferredProjects: elapsed time (in milliseconds) 2.6574 146 | Info 12 [3:43:45.359] response: 147 | {"seq":0,"type":"response","command":"compilerOptionsForInferredProjects","request_seq":1,"success":true,"body":true} 148 | Info 13 [3:43:45.359] request: 149 | {"seq":2,"type":"request","command":"updateOpen","arguments":{"openFiles":[{"file":"/tsjp-resources/sample-projects/symple/main.ts","fileContent":"const b = 1;\nconst a = b;","scriptKindName":"TS","projectRootPath":"/tsjp-resources/sample-projects/symple"}]}} 150 | Info 14 [3:43:45.362] Search path: /tsjp-resources/sample-projects/symple 151 | Info 15 [3:43:45.362] ConfigFilePresence:: Current Watches: :: File: /tsjp-resources/sample-projects/symple/tsconfig.json Currently impacted open files: RootsOfInferredProjects: OtherOpenFiles: /tsjp-resources/sample-projects/symple/main.ts Status: File added to open files impacted by this config file 152 | Info 16 [3:43:45.362] For info: /tsjp-resources/sample-projects/symple/main.ts :: Config file name: /tsjp-resources/sample-projects/symple/tsconfig.json 153 | Info 17 [3:43:45.363] Opened configuration file /tsjp-resources/sample-projects/symple/tsconfig.json 154 | Info 18 [3:43:45.365] FileWatcher:: Added:: WatchInfo: /tsjp-resources/sample-projects/symple/tsconfig.json 2000 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Config file 155 | Info 19 [3:43:45.366] event: 156 | {"seq":0,"type":"event","event":"projectLoadingStart","body":{"projectName":"/tsjp-resources/sample-projects/symple/tsconfig.json","reason":"Creating possible configured project for /tsjp-resources/sample-projects/symple/main.ts to open"}} 157 | Info 20 [3:43:45.383] DirectoryWatcher:: Added:: WatchInfo: /users/yosuke/workspace/javascript/tsjp-resources/sample-projects/symple 1 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Wild card directory 158 | Info 21 [3:43:45.385] Elapsed:: 2ms DirectoryWatcher:: Added:: WatchInfo: /users/yosuke/workspace/javascript/tsjp-resources/sample-projects/symple 1 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Wild card directory 159 | Info 22 [3:43:45.394] Starting updateGraphWorker: Project: /tsjp-resources/sample-projects/symple/tsconfig.json 160 | Info 23 [3:43:45.402] DirectoryWatcher:: Added:: WatchInfo: /users/yosuke/workspace/javascript/tsjp-resources/sample-projects/symple/node_modules 1 Project: WatchType: node_modules for closed script infos in them 161 | Info 24 [3:43:45.402] Elapsed:: 0ms DirectoryWatcher:: Added:: WatchInfo: /users/yosuke/workspace/javascript/tsjp-resources/sample-projects/symple/node_modules 1 Project: WatchType: node_modules for closed script infos in them 162 | Info 25 [3:43:46.510] DirectoryWatcher:: Added:: WatchInfo: /tsjp-resources/sample-projects/symple/node_modules/@types 1 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Type roots 163 | Info 26 [3:43:46.511] Elapsed:: 1ms DirectoryWatcher:: Added:: WatchInfo: /tsjp-resources/sample-projects/symple/node_modules/@types 1 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Type roots 164 | Info 27 [3:43:46.511] DirectoryWatcher:: Added:: WatchInfo: /tsjp-resources/sample-projects/node_modules/@types 1 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Type roots 165 | Info 28 [3:43:46.511] Elapsed:: 0ms DirectoryWatcher:: Added:: WatchInfo: /tsjp-resources/sample-projects/node_modules/@types 1 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Type roots 166 | Info 29 [3:43:46.511] DirectoryWatcher:: Added:: WatchInfo: /tsjp-resources/node_modules/@types 1 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Type roots 167 | Info 30 [3:43:46.511] Elapsed:: 0ms DirectoryWatcher:: Added:: WatchInfo: /tsjp-resources/node_modules/@types 1 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Type roots 168 | Info 31 [3:43:46.512] DirectoryWatcher:: Added:: WatchInfo: /node_modules/@types 1 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Type roots 169 | Info 32 [3:43:46.512] Elapsed:: 0ms DirectoryWatcher:: Added:: WatchInfo: /node_modules/@types 1 Project: /tsjp-resources/sample-projects/symple/tsconfig.json WatchType: Type roots 170 | Info 33 [3:43:46.512] Finishing updateGraphWorker: Project: /tsjp-resources/sample-projects/symple/tsconfig.json Version: 1 structureChanged: true Elapsed: 1118ms 171 | Info 34 [3:43:46.512] Project '/tsjp-resources/sample-projects/symple/tsconfig.json' (Configured) 172 | Info 35 [3:43:46.512] Files (6) 173 | /tsjp-resources/sample-projects/symple/node_modules/typescript/lib/lib.d.ts 174 | /tsjp-resources/sample-projects/symple/node_modules/typescript/lib/lib.es5.d.ts 175 | /tsjp-resources/sample-projects/symple/node_modules/typescript/lib/lib.dom.d.ts 176 | /tsjp-resources/sample-projects/symple/node_modules/typescript/lib/lib.webworker.importscripts.d.ts 177 | /tsjp-resources/sample-projects/symple/node_modules/typescript/lib/lib.scripthost.d.ts 178 | /tsjp-resources/sample-projects/symple/main.ts 179 | 180 | Info 36 [3:43:46.512] ----------------------------------------------- 181 | Info 37 [3:43:46.513] event: 182 | {"seq":0,"type":"event","event":"projectLoadingFinish","body":{"projectName":"/tsjp-resources/sample-projects/symple/tsconfig.json"}} 183 | Info 38 [3:43:46.515] event: 184 | {"seq":0,"type":"event","event":"telemetry","body":{"telemetryEventName":"projectInfo","payload":{"projectId":"ae5a366d488554e73554525d4a4faa320292532fc679691b1c16311b2580af1e","fileStats":{"js":0,"jsSize":0,"jsx":0,"jsxSize":0,"ts":1,"tsSize":25,"tsx":0,"tsxSize":0,"dts":5,"dtsSize":995445,"deferred":0,"deferredSize":0},"compilerOptions":{"target":"es5","module":"commonjs","strict":true,"esModuleInterop":true},"typeAcquisition":{"enable":false,"include":false,"exclude":false},"extends":false,"files":false,"include":false,"exclude":false,"compileOnSave":false,"configFileName":"tsconfig.json","projectType":"configured","languageServiceEnabled":true,"version":"3.5.1"}}} 185 | Info 39 [3:43:46.518] event: 186 | {"seq":0,"type":"event","event":"configFileDiag","body":{"triggerFile":"/tsjp-resources/sample-projects/symple/main.ts","configFile":"/tsjp-resources/sample-projects/symple/tsconfig.json","diagnostics":[]}} 187 | Info 40 [3:43:46.519] Project '/tsjp-resources/sample-projects/symple/tsconfig.json' (Configured) 0 188 | Info 40 [3:43:46.519] Files (6) 189 | /tsjp-resources/sample-projects/symple/node_modules/typescript/lib/lib.d.ts 190 | ``` 191 | 192 | --- 193 | 194 | # Looooooong 😇 195 | 196 | --- 197 | 198 | # How to read tsserver's log 199 | 200 | `{ "type": "request", ... }` means a request from the editor. 201 | 202 | `{ "command":"completionInfo", ... }` means kind of request. 203 | 204 | 205 | --- 206 | 207 | # **2. Virtual File System** 208 | 209 | --- 210 | 211 | ## Components in tsserver 212 | 213 | 214 | 215 | --- 216 | 217 | ## Language service & host 218 | 219 | - Lanage Service: Analyzes TypeScript project information(errors, types, etc...) 220 | - Lanage Service Host: Provides file information of the project to the language service 221 | 222 | ```typescript 223 | import * as ts from "typescript"; 224 | 225 | const host = { ... }; 226 | const languageService = ts.createLanguageService(host); 227 | 228 | languageService.getQuickInfoAtPosition(...); 229 | ``` 230 | 231 | --- 232 | 233 | ## LanguageServiceHost I/F 234 | 235 | ```typescript 236 | interface LanguageServiceHost { 237 | getCompilationSettings(): CompilerOptions; 238 | 239 | getScriptFileNames(): string[]; 240 | 241 | getScriptSnapshot(fileName: string): IScriptSnapshot | undefined; 242 | 243 | getScriptVersion(fileName: string): string; 244 | getCurrentDirectory(): string; 245 | getDefaultLibFileName(options: CompilerOptions): string; 246 | 247 | /* skip the rest */ 248 | } 249 | ``` 250 | 251 | --- 252 | 253 | ## Language service does not depend on Node.js's fs 254 | 255 | --- 256 | 257 | ## We can run it on Web browser 258 | 259 | --- 260 | 261 | 262 | 263 | --- 264 | 265 | ## How many files are opened ? 266 | 267 | --- 268 | 269 | 270 | 271 | --- 272 | 273 | # **3. Mutation of script** 274 | 275 | --- 276 | 277 | ![cat_typing](https://media.giphy.com/media/lJNoBCvQYp7nq/giphy.gif) 278 | 279 | --- 280 | 281 | # LSHost needs to accept "what developer changes" 282 | 283 | 284 | 285 | 286 | --- 287 | 288 | ## 2 kinds of files watched by tsserver: 289 | 290 | * Immutable: e.g. dom.lib.d.ts or libraries' .ts files 291 | * **Mutable: Files you're editing** 292 | * The changes may not be applied on your file system 293 | 294 | It means that tsserver should have not only virtual file sysytem but also the mirror of editor's buffers. 295 | 296 | 297 | 298 | tsserver upgrades data structure of the virtual file to `ScriptVersionCache` (SVC) when accepting the file changes. 299 | 300 | --- 301 | 302 | ## Script version cache 303 | 304 | 305 | 306 | --- 307 | 308 | 309 | 310 | --- 311 | 312 | ## Data structure of SVC's snapshot 313 | 314 | 315 | 316 | --- 317 | 318 | ## Rope 319 | 320 | 321 | 322 | [*Rope data structure*](https://en.wikipedia.org/wiki/Rope_(data_structure)) 323 | 324 | * O(log N) time complexity for insertion/deletion 325 | 326 | --- 327 | 328 | # **4. Mutation of AST** 329 | 330 | --- 331 | 332 | 333 | 334 | --- 335 | 336 | ## Changes info 337 | 338 | ```typescript 339 | interface LanguageServiceHost { 340 | 341 | getScriptVersion(fileName: string): string; 342 | getScriptSnapshot(fileName: string): IScriptSnapshot | undefined; 343 | 344 | /* skip the rest */ 345 | } 346 | 347 | interface IScriptSnapshot { 348 | /** Gets a portion of the script snapshot specified by [start, end). */ 349 | getText(start: number, end: number): string; 350 | /** Gets the length of this script snapshot. */ 351 | getLength(): number; 352 | /** 353 | * Gets the TextChangeRange that describe how the text changed between this text and 354 | * an older version. This information is used by the incremental parser to determine 355 | * what sections of the script need to be re-parsed. 'undefined' can be returned if the 356 | * change range cannot be determined. However, in that case, incremental parsing will 357 | * not happen and the entire document will be re - parsed. 358 | */ 359 | getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange | undefined; 360 | /** Releases all resources held by this script snapshot */ 361 | dispose?(): void; 362 | } 363 | ``` 364 | 365 | --- 366 | 367 | ### TextChangeRange: 368 | 369 | > This information is used by the **incremental parser** to determine 370 | > what sections of the script need to be re-parsed. 371 | 372 | --- 373 | 374 | ## What's incremental parsing ? 375 | 376 | --- 377 | 378 | ### Example 379 | 380 | 381 | 382 | --- 383 | 384 | ### AST before 385 | 386 | 387 | 388 | 389 | --- 390 | 391 | ### AST after 392 | 393 | 394 | 395 | 396 | --- 397 | 398 | ## 1. Get textChangeRange 399 | 400 | 401 | 402 | --- 403 | 404 | ## 1. Gets textChangeRange 405 | 406 | 407 | 408 | --- 409 | 410 | ## 2. Make the actual change 411 | 412 | 413 | 414 | --- 415 | 416 | ## 3. Visit each old nodes and mark intersected 417 | 418 | 419 | 420 | --- 421 | 422 | ## FYI: Performance of incremental parsing 423 | 424 | --- 425 | 426 | 427 | 428 | 429 | 430 | * x-axis: The numbers of lines of original source code 431 | * y-axis: Time [msec] to parse for 60 times insertion 432 | 433 | --- 434 | 435 | ### source 436 | 437 | [https://github.com/Quramy/tsjp-resources/blob/master/ls-sample/src/parse-performance.ts](https://github.com/Quramy/tsjp-resources/blob/master/ls-sample/src/parse-performance.ts) 438 | 439 | ```typescript 440 | import * as ts from "typescript"; 441 | 442 | const insertText = "\ndeclare namespace hoge { export function fooobar(): void; }" 443 | 444 | const rangeItems = (n: number) => { 445 | const arr = []; 446 | for(let i = 0; i < n; i++) { 447 | arr[i] = i; 448 | } 449 | return arr; 450 | }; 451 | 452 | function checkPerf(lines: number) { 453 | const originalSourceText = `declare namespace fuga { 454 | ${rangeItems(lines - 3).map((_, i) => " function fn" + i + "(): number;").join("\n")} 455 | } 456 | `; 457 | 458 | const origSource = ts.createSourceFile("index.d.ts", originalSourceText, ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS); 459 | 460 | const s1 = Date.now(); 461 | let sourceFile = origSource; 462 | for (let i = 0; i < insertText.length; i++) { 463 | const pos = sourceFile.end; 464 | sourceFile = ts.updateSourceFile(sourceFile, sourceFile.getText() + insertText[i], { newLength: 1, span: { start: pos, length: 0 } }); 465 | } 466 | const e1 = Date.now(); 467 | 468 | const s2 = Date.now(); 469 | for (let i = 0; i < insertText.length; i++) { 470 | const origSource = ts.createSourceFile("index.d.ts", originalSourceText + insertText.slice(0, i), ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS); 471 | } 472 | const e2 = Date.now(); 473 | 474 | console.log("==================================") 475 | console.log(`originalSourceText lines: ${originalSourceText.split("\n").length}`); 476 | console.log(`originalSourceText length: ${originalSourceText.length}`); 477 | console.log(`insertText length: ${insertText.length}`); 478 | console.log(`elapsed time with incremental parsing: ${e1 - s1}`); 479 | console.log(`elapsed time with non-incremental parsing: ${e2 - s2}`); 480 | console.log(""); 481 | } 482 | 483 | checkPerf(100); 484 | checkPerf(500); 485 | checkPerf(1000); 486 | checkPerf(5000); 487 | checkPerf(10000); 488 | ``` 489 | 490 | -------------------------------------------------------------------------------- /ls-sample/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^12.0.3": 6 | version "12.0.3" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.3.tgz#5d8d24e0033fc6393efadc85cb59c1f638095c9a" 8 | integrity sha512-zkOxCS/fA+3SsdA+9Yun0iANxzhQRiNwTvJSr6N95JhuJ/x27z9G2URx1Jpt3zYFfCGUXZGL5UDxt5eyLE7wgw== 9 | 10 | "@types/prop-types@*": 11 | version "15.7.1" 12 | resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.1.tgz#f1a11e7babb0c3cad68100be381d1e064c68f1f6" 13 | integrity sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg== 14 | 15 | "@types/react@^16.8.19": 16 | version "16.8.19" 17 | resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.19.tgz#629154ef05e2e1985cdde94477deefd823ad9be3" 18 | integrity sha512-QzEzjrd1zFzY9cDlbIiFvdr+YUmefuuRYrPxmkwG0UQv5XF35gFIi7a95m1bNVcFU0VimxSZ5QVGSiBmlggQXQ== 19 | dependencies: 20 | "@types/prop-types" "*" 21 | csstype "^2.2.0" 22 | 23 | "@types/strip-bom@^3.0.0": 24 | version "3.0.0" 25 | resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" 26 | integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I= 27 | 28 | "@types/strip-json-comments@0.0.30": 29 | version "0.0.30" 30 | resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" 31 | integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== 32 | 33 | arg@^4.1.0: 34 | version "4.1.0" 35 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.0.tgz#583c518199419e0037abb74062c37f8519e575f0" 36 | integrity sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg== 37 | 38 | array-find-index@^1.0.1: 39 | version "1.0.2" 40 | resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" 41 | integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= 42 | 43 | balanced-match@^1.0.0: 44 | version "1.0.0" 45 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 46 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 47 | 48 | brace-expansion@^1.1.7: 49 | version "1.1.11" 50 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 51 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 52 | dependencies: 53 | balanced-match "^1.0.0" 54 | concat-map "0.0.1" 55 | 56 | buffer-from@^1.0.0: 57 | version "1.1.1" 58 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 59 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 60 | 61 | camelcase-keys@^2.0.0: 62 | version "2.1.0" 63 | resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" 64 | integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= 65 | dependencies: 66 | camelcase "^2.0.0" 67 | map-obj "^1.0.0" 68 | 69 | camelcase@^2.0.0: 70 | version "2.1.1" 71 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" 72 | integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= 73 | 74 | concat-map@0.0.1: 75 | version "0.0.1" 76 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 77 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 78 | 79 | csstype@^2.2.0: 80 | version "2.6.5" 81 | resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.5.tgz#1cd1dff742ebf4d7c991470ae71e12bb6751e034" 82 | integrity sha512-JsTaiksRsel5n7XwqPAfB0l3TFKdpjW/kgAELf9vrb5adGA7UCPLajKK5s3nFrcFm3Rkyp/Qkgl73ENc1UY3cA== 83 | 84 | currently-unhandled@^0.4.1: 85 | version "0.4.1" 86 | resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" 87 | integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= 88 | dependencies: 89 | array-find-index "^1.0.1" 90 | 91 | dateformat@~1.0.4-1.2.3: 92 | version "1.0.12" 93 | resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" 94 | integrity sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk= 95 | dependencies: 96 | get-stdin "^4.0.1" 97 | meow "^3.3.0" 98 | 99 | debounce@^1.0.0: 100 | version "1.2.0" 101 | resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" 102 | integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== 103 | 104 | decamelize@^1.1.2: 105 | version "1.2.0" 106 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 107 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 108 | 109 | diff@^4.0.1: 110 | version "4.0.1" 111 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" 112 | integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== 113 | 114 | dynamic-dedupe@^0.3.0: 115 | version "0.3.0" 116 | resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1" 117 | integrity sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE= 118 | dependencies: 119 | xtend "^4.0.0" 120 | 121 | error-ex@^1.2.0: 122 | version "1.3.2" 123 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" 124 | integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== 125 | dependencies: 126 | is-arrayish "^0.2.1" 127 | 128 | filewatcher@~3.0.0: 129 | version "3.0.1" 130 | resolved "https://registry.yarnpkg.com/filewatcher/-/filewatcher-3.0.1.tgz#f4a1957355ddaf443ccd78a895f3d55e23c8a034" 131 | integrity sha1-9KGVc1Xdr0Q8zXiolfPVXiPIoDQ= 132 | dependencies: 133 | debounce "^1.0.0" 134 | 135 | find-up@^1.0.0: 136 | version "1.1.2" 137 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" 138 | integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= 139 | dependencies: 140 | path-exists "^2.0.0" 141 | pinkie-promise "^2.0.0" 142 | 143 | fs.realpath@^1.0.0: 144 | version "1.0.0" 145 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 146 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 147 | 148 | get-stdin@^4.0.1: 149 | version "4.0.1" 150 | resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" 151 | integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= 152 | 153 | glob@^7.1.3: 154 | version "7.1.4" 155 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" 156 | integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== 157 | dependencies: 158 | fs.realpath "^1.0.0" 159 | inflight "^1.0.4" 160 | inherits "2" 161 | minimatch "^3.0.4" 162 | once "^1.3.0" 163 | path-is-absolute "^1.0.0" 164 | 165 | graceful-fs@^4.1.2: 166 | version "4.1.15" 167 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" 168 | integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== 169 | 170 | growly@^1.3.0: 171 | version "1.3.0" 172 | resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" 173 | integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= 174 | 175 | hosted-git-info@^2.1.4: 176 | version "2.7.1" 177 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" 178 | integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== 179 | 180 | indent-string@^2.1.0: 181 | version "2.1.0" 182 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" 183 | integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= 184 | dependencies: 185 | repeating "^2.0.0" 186 | 187 | inflight@^1.0.4: 188 | version "1.0.6" 189 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 190 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 191 | dependencies: 192 | once "^1.3.0" 193 | wrappy "1" 194 | 195 | inherits@2: 196 | version "2.0.3" 197 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 198 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 199 | 200 | is-arrayish@^0.2.1: 201 | version "0.2.1" 202 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 203 | integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= 204 | 205 | is-finite@^1.0.0: 206 | version "1.0.2" 207 | resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" 208 | integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= 209 | dependencies: 210 | number-is-nan "^1.0.0" 211 | 212 | is-utf8@^0.2.0: 213 | version "0.2.1" 214 | resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" 215 | integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= 216 | 217 | is-wsl@^1.1.0: 218 | version "1.1.0" 219 | resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" 220 | integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= 221 | 222 | isexe@^2.0.0: 223 | version "2.0.0" 224 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 225 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 226 | 227 | load-json-file@^1.0.0: 228 | version "1.1.0" 229 | resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" 230 | integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= 231 | dependencies: 232 | graceful-fs "^4.1.2" 233 | parse-json "^2.2.0" 234 | pify "^2.0.0" 235 | pinkie-promise "^2.0.0" 236 | strip-bom "^2.0.0" 237 | 238 | loud-rejection@^1.0.0: 239 | version "1.6.0" 240 | resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" 241 | integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= 242 | dependencies: 243 | currently-unhandled "^0.4.1" 244 | signal-exit "^3.0.0" 245 | 246 | make-error@^1.1.1: 247 | version "1.3.5" 248 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" 249 | integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== 250 | 251 | map-obj@^1.0.0, map-obj@^1.0.1: 252 | version "1.0.1" 253 | resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" 254 | integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= 255 | 256 | meow@^3.3.0: 257 | version "3.7.0" 258 | resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" 259 | integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= 260 | dependencies: 261 | camelcase-keys "^2.0.0" 262 | decamelize "^1.1.2" 263 | loud-rejection "^1.0.0" 264 | map-obj "^1.0.1" 265 | minimist "^1.1.3" 266 | normalize-package-data "^2.3.4" 267 | object-assign "^4.0.1" 268 | read-pkg-up "^1.0.1" 269 | redent "^1.0.0" 270 | trim-newlines "^1.0.0" 271 | 272 | minimatch@^3.0.4: 273 | version "3.0.4" 274 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 275 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 276 | dependencies: 277 | brace-expansion "^1.1.7" 278 | 279 | minimist@0.0.8: 280 | version "0.0.8" 281 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 282 | integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= 283 | 284 | minimist@^1.1.3: 285 | version "1.2.0" 286 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" 287 | integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= 288 | 289 | mkdirp@^0.5.1: 290 | version "0.5.1" 291 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 292 | integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= 293 | dependencies: 294 | minimist "0.0.8" 295 | 296 | node-notifier@^5.4.0: 297 | version "5.4.0" 298 | resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.0.tgz#7b455fdce9f7de0c63538297354f3db468426e6a" 299 | integrity sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ== 300 | dependencies: 301 | growly "^1.3.0" 302 | is-wsl "^1.1.0" 303 | semver "^5.5.0" 304 | shellwords "^0.1.1" 305 | which "^1.3.0" 306 | 307 | normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: 308 | version "2.5.0" 309 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" 310 | integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== 311 | dependencies: 312 | hosted-git-info "^2.1.4" 313 | resolve "^1.10.0" 314 | semver "2 || 3 || 4 || 5" 315 | validate-npm-package-license "^3.0.1" 316 | 317 | number-is-nan@^1.0.0: 318 | version "1.0.1" 319 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 320 | integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= 321 | 322 | object-assign@^4.0.1: 323 | version "4.1.1" 324 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 325 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 326 | 327 | once@^1.3.0: 328 | version "1.4.0" 329 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 330 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 331 | dependencies: 332 | wrappy "1" 333 | 334 | parse-json@^2.2.0: 335 | version "2.2.0" 336 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" 337 | integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= 338 | dependencies: 339 | error-ex "^1.2.0" 340 | 341 | path-exists@^2.0.0: 342 | version "2.1.0" 343 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" 344 | integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= 345 | dependencies: 346 | pinkie-promise "^2.0.0" 347 | 348 | path-is-absolute@^1.0.0: 349 | version "1.0.1" 350 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 351 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 352 | 353 | path-parse@^1.0.6: 354 | version "1.0.6" 355 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 356 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== 357 | 358 | path-type@^1.0.0: 359 | version "1.1.0" 360 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" 361 | integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= 362 | dependencies: 363 | graceful-fs "^4.1.2" 364 | pify "^2.0.0" 365 | pinkie-promise "^2.0.0" 366 | 367 | pify@^2.0.0: 368 | version "2.3.0" 369 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 370 | integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= 371 | 372 | pinkie-promise@^2.0.0: 373 | version "2.0.1" 374 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 375 | integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= 376 | dependencies: 377 | pinkie "^2.0.0" 378 | 379 | pinkie@^2.0.0: 380 | version "2.0.4" 381 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 382 | integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= 383 | 384 | read-pkg-up@^1.0.1: 385 | version "1.0.1" 386 | resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" 387 | integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= 388 | dependencies: 389 | find-up "^1.0.0" 390 | read-pkg "^1.0.0" 391 | 392 | read-pkg@^1.0.0: 393 | version "1.1.0" 394 | resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" 395 | integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= 396 | dependencies: 397 | load-json-file "^1.0.0" 398 | normalize-package-data "^2.3.2" 399 | path-type "^1.0.0" 400 | 401 | redent@^1.0.0: 402 | version "1.0.0" 403 | resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" 404 | integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= 405 | dependencies: 406 | indent-string "^2.1.0" 407 | strip-indent "^1.0.1" 408 | 409 | repeating@^2.0.0: 410 | version "2.0.1" 411 | resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" 412 | integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= 413 | dependencies: 414 | is-finite "^1.0.0" 415 | 416 | resolve@^1.0.0, resolve@^1.10.0: 417 | version "1.11.0" 418 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232" 419 | integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw== 420 | dependencies: 421 | path-parse "^1.0.6" 422 | 423 | rimraf@^2.6.1: 424 | version "2.6.3" 425 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" 426 | integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== 427 | dependencies: 428 | glob "^7.1.3" 429 | 430 | "semver@2 || 3 || 4 || 5", semver@^5.5.0: 431 | version "5.7.0" 432 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" 433 | integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== 434 | 435 | shellwords@^0.1.1: 436 | version "0.1.1" 437 | resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" 438 | integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== 439 | 440 | signal-exit@^3.0.0: 441 | version "3.0.2" 442 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 443 | integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= 444 | 445 | source-map-support@^0.5.6: 446 | version "0.5.12" 447 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" 448 | integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== 449 | dependencies: 450 | buffer-from "^1.0.0" 451 | source-map "^0.6.0" 452 | 453 | source-map@^0.6.0: 454 | version "0.6.1" 455 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 456 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 457 | 458 | spdx-correct@^3.0.0: 459 | version "3.1.0" 460 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" 461 | integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== 462 | dependencies: 463 | spdx-expression-parse "^3.0.0" 464 | spdx-license-ids "^3.0.0" 465 | 466 | spdx-exceptions@^2.1.0: 467 | version "2.2.0" 468 | resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" 469 | integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== 470 | 471 | spdx-expression-parse@^3.0.0: 472 | version "3.0.0" 473 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" 474 | integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== 475 | dependencies: 476 | spdx-exceptions "^2.1.0" 477 | spdx-license-ids "^3.0.0" 478 | 479 | spdx-license-ids@^3.0.0: 480 | version "3.0.4" 481 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz#75ecd1a88de8c184ef015eafb51b5b48bfd11bb1" 482 | integrity sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA== 483 | 484 | strip-bom@^2.0.0: 485 | version "2.0.0" 486 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" 487 | integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= 488 | dependencies: 489 | is-utf8 "^0.2.0" 490 | 491 | strip-bom@^3.0.0: 492 | version "3.0.0" 493 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 494 | integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= 495 | 496 | strip-indent@^1.0.1: 497 | version "1.0.1" 498 | resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" 499 | integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= 500 | dependencies: 501 | get-stdin "^4.0.1" 502 | 503 | strip-json-comments@^2.0.0: 504 | version "2.0.1" 505 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 506 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 507 | 508 | tree-kill@^1.2.1: 509 | version "1.2.1" 510 | resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.1.tgz#5398f374e2f292b9dcc7b2e71e30a5c3bb6c743a" 511 | integrity sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q== 512 | 513 | trim-newlines@^1.0.0: 514 | version "1.0.0" 515 | resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" 516 | integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= 517 | 518 | ts-node-dev@^1.0.0-pre.39: 519 | version "1.0.0-pre.39" 520 | resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.0.0-pre.39.tgz#17c3f6350c974a16dd9b18b67577b1786b88973d" 521 | integrity sha512-yOg9nMAi6U2HcAkhnFuWxfg53XDqpdbeBESo+7DfmlDpQX4RrEzBNV6szOjlm/OH0KiRgG5J0emvg/BS/gQmXQ== 522 | dependencies: 523 | dateformat "~1.0.4-1.2.3" 524 | dynamic-dedupe "^0.3.0" 525 | filewatcher "~3.0.0" 526 | minimist "^1.1.3" 527 | mkdirp "^0.5.1" 528 | node-notifier "^5.4.0" 529 | resolve "^1.0.0" 530 | rimraf "^2.6.1" 531 | tree-kill "^1.2.1" 532 | ts-node "*" 533 | tsconfig "^7.0.0" 534 | 535 | ts-node@*: 536 | version "8.2.0" 537 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.2.0.tgz#4a89754b00560bb24cd54526e1685fa38c45f240" 538 | integrity sha512-m8XQwUurkbYqXrKqr3WHCW310utRNvV5OnRVeISeea7LoCWVcdfeB/Ntl8JYWFh+WRoUAdBgESrzKochQt7sMw== 539 | dependencies: 540 | arg "^4.1.0" 541 | diff "^4.0.1" 542 | make-error "^1.1.1" 543 | source-map-support "^0.5.6" 544 | yn "^3.0.0" 545 | 546 | tsconfig@^7.0.0: 547 | version "7.0.0" 548 | resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" 549 | integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== 550 | dependencies: 551 | "@types/strip-bom" "^3.0.0" 552 | "@types/strip-json-comments" "0.0.30" 553 | strip-bom "^3.0.0" 554 | strip-json-comments "^2.0.0" 555 | 556 | typescript@^3.5.1: 557 | version "3.5.1" 558 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" 559 | integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== 560 | 561 | validate-npm-package-license@^3.0.1: 562 | version "3.0.4" 563 | resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" 564 | integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== 565 | dependencies: 566 | spdx-correct "^3.0.0" 567 | spdx-expression-parse "^3.0.0" 568 | 569 | which@^1.3.0: 570 | version "1.3.1" 571 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 572 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 573 | dependencies: 574 | isexe "^2.0.0" 575 | 576 | wrappy@1: 577 | version "1.0.2" 578 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 579 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 580 | 581 | xtend@^4.0.0: 582 | version "4.0.1" 583 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 584 | integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= 585 | 586 | yn@^3.0.0: 587 | version "3.1.0" 588 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.0.tgz#fcbe2db63610361afcc5eb9e0ac91e976d046114" 589 | integrity sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg== 590 | --------------------------------------------------------------------------------