├── userTests ├── acorn │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── clone │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── util │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── zone.js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── ajv │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── antd │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── assert │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── bcryptjs │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── bluebird │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── jimp │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── lodash │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── mobx │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── mqtt │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── npmlog │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── rxjs │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── sift │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── soap │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── vue │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── vuex │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── xlsx │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── async │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── axios │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── debug │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── minimatch │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── redux │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── sugar │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── tslint │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── typescript-eslint-parser │ ├── fixtures │ │ └── project │ │ │ ├── file.ts │ │ │ ├── something.ts │ │ │ └── tsconfig.json │ ├── tsconfig.json │ ├── package.json │ └── index.ts ├── uglify-js │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── xpath │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── clear-require │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── graceful-fs │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── keycode │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── log4js │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── moment │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── should │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── electron │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── immutable │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── isobject │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── reselect │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── create-react-app │ ├── chalk-override.d.ts │ ├── test.json │ └── tsconfig.json ├── discord.js │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── enhanced-resolve │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── firebase │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── follow-redirects │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── jsonschema │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── octokit-rest │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── portfinder │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── protobufjs │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── url-search-params │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── bignumber.js │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── content-disposition │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── localforage │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── adonis-framework │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── eventemitter2 │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── eventemitter3 │ ├── index.ts │ ├── tsconfig.json │ └── package.json ├── axios-src │ ├── test.json │ └── tsconfig.json ├── fp-ts │ └── test.json ├── npm │ ├── test.json │ └── tsconfig.json ├── effect │ └── test.json ├── test262 │ ├── test.json │ └── tsconfig.json ├── webpack │ └── test.json ├── grunt │ ├── test.json │ └── tsconfig.json ├── type-fest │ └── test.json ├── xterm.js │ └── test.json ├── TypeScript-Node-Starter │ └── test.json ├── puppeteer │ └── test.json ├── TypeScript-WeChat-Starter │ └── test.json ├── ts-toolbelt │ ├── tsconfig.json │ ├── package.json │ └── index.ts ├── literal-no-oom │ └── tsconfig.json ├── TypeScript-React-Native-Starter │ └── test.json ├── rxjs-src │ └── build.sh ├── postcss │ ├── package.json │ ├── tsconfig.json │ └── index.ts ├── formik │ ├── tsconfig.json │ ├── package.json │ └── index.tsx ├── renovate │ └── build.sh ├── arktype │ └── build.sh ├── prettier │ └── build.sh ├── vue-next │ └── build.sh ├── angular │ └── build.sh ├── pyright │ └── build.sh ├── vscode │ └── build.sh ├── typescript-eslint │ └── build.sh ├── chrome-devtools-frontend-next │ └── build.sh ├── office-ui-fabric │ └── build.sh └── azure-sdk │ └── build.sh ├── testResources ├── scriptProject │ ├── main.ts │ └── build.sh ├── simpleProject │ ├── main.ts │ └── tsconfig.json └── scriptPrettier │ └── build.sh ├── src ├── tsconfig.json ├── utils │ ├── markdownUtils.ts │ ├── exerciseServerConstants.ts │ ├── hashStackTrace.ts │ ├── userTestUtils.ts │ ├── execUtils.ts │ ├── packageUtils.ts │ ├── installPackages.ts │ ├── overlayFS.ts │ ├── gitUtils.ts │ ├── projectGraph.ts │ └── getTscErrors.ts ├── listUserTestRepos.ts ├── checkGithubRepos.ts ├── checkUserTestRepos.ts ├── listTopRepos.ts ├── postGithubIssue.ts └── postGithubComments.ts ├── tsconfig.json ├── tsconfig.settings.json ├── jest.config.js ├── test ├── tsconfig.json ├── projectGraph.test.ts ├── getTscErrors.test.ts └── main.test.ts ├── SUPPORT.md ├── CODE_OF_CONDUCT.md ├── azure-pipelines-gitTests-tsserver-js.yml ├── azure-pipelines-gitTests-tsserver-ts.yml ├── azure-pipelines-compliance.yml ├── .github └── workflows │ └── ci.yml ├── CONTRIBUTING.md ├── LICENSE ├── package.json ├── .vscode ├── tasks.json └── launch.json ├── azure-pipelines-gitTests.yml ├── .gitignore ├── SECURITY.md ├── azure-pipelines-gitTests-template.yml ├── README.md └── azure-pipelines-userTests.yml /userTests/acorn/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('acorn'); -------------------------------------------------------------------------------- /userTests/clone/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('clone'); -------------------------------------------------------------------------------- /userTests/util/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('util'); -------------------------------------------------------------------------------- /userTests/zone.js/index.ts: -------------------------------------------------------------------------------- 1 | Zone.assertZonePatched 2 | -------------------------------------------------------------------------------- /userTests/ajv/index.ts: -------------------------------------------------------------------------------- 1 | import ajv = require("ajv"); 2 | -------------------------------------------------------------------------------- /userTests/antd/index.ts: -------------------------------------------------------------------------------- 1 | import antd = require("antd"); 2 | -------------------------------------------------------------------------------- /userTests/assert/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('assert'); -------------------------------------------------------------------------------- /userTests/bcryptjs/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('bcryptjs'); -------------------------------------------------------------------------------- /userTests/bluebird/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('bluebird'); -------------------------------------------------------------------------------- /userTests/jimp/index.ts: -------------------------------------------------------------------------------- 1 | import jimp = require("jimp"); 2 | -------------------------------------------------------------------------------- /userTests/lodash/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('lodash'); -------------------------------------------------------------------------------- /userTests/mobx/index.ts: -------------------------------------------------------------------------------- 1 | import mobx = require("mobx"); 2 | -------------------------------------------------------------------------------- /userTests/mqtt/index.ts: -------------------------------------------------------------------------------- 1 | import mqtt = require("mqtt"); 2 | -------------------------------------------------------------------------------- /userTests/npmlog/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('npmlog'); -------------------------------------------------------------------------------- /userTests/rxjs/index.ts: -------------------------------------------------------------------------------- 1 | import rxjs = require("rxjs"); 2 | -------------------------------------------------------------------------------- /userTests/sift/index.ts: -------------------------------------------------------------------------------- 1 | import sift = require("sift"); 2 | -------------------------------------------------------------------------------- /userTests/soap/index.ts: -------------------------------------------------------------------------------- 1 | import soap = require("soap"); 2 | -------------------------------------------------------------------------------- /userTests/vue/index.ts: -------------------------------------------------------------------------------- 1 | import vue = require("vue"); 2 | -------------------------------------------------------------------------------- /userTests/vuex/index.ts: -------------------------------------------------------------------------------- 1 | import vuex = require("vuex"); 2 | -------------------------------------------------------------------------------- /userTests/xlsx/index.ts: -------------------------------------------------------------------------------- 1 | import xlsx = require("xlsx"); 2 | -------------------------------------------------------------------------------- /userTests/async/index.ts: -------------------------------------------------------------------------------- 1 | import async_ = require('async'); 2 | -------------------------------------------------------------------------------- /userTests/axios/index.ts: -------------------------------------------------------------------------------- 1 | import axios = require("axios"); 2 | -------------------------------------------------------------------------------- /userTests/debug/index.ts: -------------------------------------------------------------------------------- 1 | import debug = require('debug'); 2 | -------------------------------------------------------------------------------- /userTests/minimatch/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('minimatch'); -------------------------------------------------------------------------------- /userTests/redux/index.ts: -------------------------------------------------------------------------------- 1 | import redux = require("redux"); 2 | -------------------------------------------------------------------------------- /userTests/sugar/index.ts: -------------------------------------------------------------------------------- 1 | import sugar = require("sugar"); 2 | -------------------------------------------------------------------------------- /userTests/tslint/index.ts: -------------------------------------------------------------------------------- 1 | import tslint = require("tslint"); -------------------------------------------------------------------------------- /userTests/typescript-eslint-parser/fixtures/project/file.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /userTests/uglify-js/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('uglify-js'); -------------------------------------------------------------------------------- /userTests/xpath/index.ts: -------------------------------------------------------------------------------- 1 | import xpath = require("xpath"); 2 | -------------------------------------------------------------------------------- /userTests/clear-require/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('clear-require'); -------------------------------------------------------------------------------- /userTests/graceful-fs/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('graceful-fs'); -------------------------------------------------------------------------------- /userTests/keycode/index.ts: -------------------------------------------------------------------------------- 1 | import keycode = require("keycode"); 2 | -------------------------------------------------------------------------------- /userTests/log4js/index.ts: -------------------------------------------------------------------------------- 1 | import log4js = require("log4js"); 2 | -------------------------------------------------------------------------------- /userTests/moment/index.ts: -------------------------------------------------------------------------------- 1 | import moment = require("moment"); 2 | -------------------------------------------------------------------------------- /userTests/should/index.ts: -------------------------------------------------------------------------------- 1 | import should = require("should"); 2 | -------------------------------------------------------------------------------- /userTests/electron/index.ts: -------------------------------------------------------------------------------- 1 | import electron = require("electron"); 2 | -------------------------------------------------------------------------------- /userTests/immutable/index.ts: -------------------------------------------------------------------------------- 1 | import immutable = require("immutable"); 2 | -------------------------------------------------------------------------------- /userTests/isobject/index.ts: -------------------------------------------------------------------------------- 1 | import isobject = require("isobject"); 2 | -------------------------------------------------------------------------------- /userTests/reselect/index.ts: -------------------------------------------------------------------------------- 1 | import reselect = require("reselect"); 2 | -------------------------------------------------------------------------------- /userTests/create-react-app/chalk-override.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'chalk'; 2 | -------------------------------------------------------------------------------- /userTests/discord.js/index.ts: -------------------------------------------------------------------------------- 1 | import discord_js = require("discord.js"); 2 | -------------------------------------------------------------------------------- /userTests/enhanced-resolve/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('enhanced-resolve'); -------------------------------------------------------------------------------- /userTests/firebase/index.ts: -------------------------------------------------------------------------------- 1 | import firebase = require("firebase/app"); 2 | -------------------------------------------------------------------------------- /userTests/follow-redirects/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('follow-redirects'); -------------------------------------------------------------------------------- /userTests/jsonschema/index.ts: -------------------------------------------------------------------------------- 1 | import jsonschema = require("jsonschema"); 2 | -------------------------------------------------------------------------------- /userTests/octokit-rest/index.ts: -------------------------------------------------------------------------------- 1 | import github = require("@octokit/rest"); 2 | -------------------------------------------------------------------------------- /userTests/portfinder/index.ts: -------------------------------------------------------------------------------- 1 | import portfinder = require("portfinder"); 2 | -------------------------------------------------------------------------------- /userTests/protobufjs/index.ts: -------------------------------------------------------------------------------- 1 | import protobufjs = require("protobufjs"); 2 | -------------------------------------------------------------------------------- /userTests/url-search-params/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('url-search-params'); -------------------------------------------------------------------------------- /userTests/bignumber.js/index.ts: -------------------------------------------------------------------------------- 1 | import bignumber_js = require("bignumber.js"); 2 | -------------------------------------------------------------------------------- /userTests/content-disposition/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('content-disposition'); -------------------------------------------------------------------------------- /userTests/localforage/index.ts: -------------------------------------------------------------------------------- 1 | import localforage = require("localforage"); 2 | -------------------------------------------------------------------------------- /userTests/adonis-framework/index.ts: -------------------------------------------------------------------------------- 1 | import x = require('adonis-framework/src/View'); 2 | -------------------------------------------------------------------------------- /userTests/eventemitter2/index.ts: -------------------------------------------------------------------------------- 1 | import eventemitter2 = require("eventemitter2"); 2 | -------------------------------------------------------------------------------- /userTests/eventemitter3/index.ts: -------------------------------------------------------------------------------- 1 | import eventemitter3 = require("eventemitter3"); 2 | -------------------------------------------------------------------------------- /userTests/typescript-eslint-parser/fixtures/project/something.ts: -------------------------------------------------------------------------------- 1 | export const something = () => {}; 2 | -------------------------------------------------------------------------------- /testResources/scriptProject/main.ts: -------------------------------------------------------------------------------- 1 | export function noDefaultsAllowed(p = 1) { 2 | return p 3 | } 4 | -------------------------------------------------------------------------------- /testResources/simpleProject/main.ts: -------------------------------------------------------------------------------- 1 | export function noDefaultsAllowed(p = 1) { 2 | return p 3 | } 4 | -------------------------------------------------------------------------------- /testResources/simpleProject/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /testResources/scriptProject/build.sh: -------------------------------------------------------------------------------- 1 | node $TS/built/local/tsc.js --skipLibCheck --incremental false --pretty false main.ts 2 | -------------------------------------------------------------------------------- /userTests/axios-src/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/axios/axios.git", 3 | "types": ["node"] 4 | } 5 | -------------------------------------------------------------------------------- /userTests/typescript-eslint-parser/fixtures/project/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { "types": ["node"] } 3 | } 4 | -------------------------------------------------------------------------------- /userTests/fp-ts/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/gcanti/fp-ts.git", 3 | "types": ["node", "jest"] 4 | } 5 | -------------------------------------------------------------------------------- /userTests/npm/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/npm/cli.git", 3 | "branch": "latest", 4 | "types": ["node"] 5 | } 6 | -------------------------------------------------------------------------------- /userTests/typescript-eslint-parser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "types": [] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /userTests/effect/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/Effect-TS/effect.git", 3 | "branch": "main", 4 | "types": [] 5 | } 6 | -------------------------------------------------------------------------------- /userTests/test262/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/tc39/test262.git", 3 | "branch": "main", 4 | "types": [] 5 | } 6 | -------------------------------------------------------------------------------- /userTests/webpack/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/webpack/webpack.git", 3 | "branch": "main", 4 | "types": [] 5 | } 6 | -------------------------------------------------------------------------------- /userTests/grunt/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/gruntjs/grunt.git", 3 | "branch": "main", 4 | "types": ["node"] 5 | } 6 | -------------------------------------------------------------------------------- /userTests/type-fest/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/sindresorhus/type-fest.git", 3 | "branch": "main", 4 | "types": [] 5 | } 6 | -------------------------------------------------------------------------------- /userTests/xterm.js/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/xtermjs/xterm.js.git", 3 | "branch": "master", 4 | "types": [] 5 | } 6 | -------------------------------------------------------------------------------- /userTests/TypeScript-Node-Starter/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/Microsoft/TypeScript-Node-Starter.git", 3 | "types": [] 4 | } 5 | -------------------------------------------------------------------------------- /userTests/puppeteer/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/GoogleChrome/puppeteer.git", 3 | "branch": "main", 4 | "types": [] 5 | } 6 | -------------------------------------------------------------------------------- /userTests/TypeScript-WeChat-Starter/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/Microsoft/TypeScript-WeChat-Starter.git", 3 | "types": [] 4 | } 5 | -------------------------------------------------------------------------------- /userTests/ajv/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/create-react-app/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/facebook/create-react-app.git", 3 | "branch": "main", 4 | "types": [] 5 | } 6 | -------------------------------------------------------------------------------- /userTests/ts-toolbelt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "types": [], 5 | "lib": ["es6"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/axios/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/electron/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/firebase/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/isobject/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/keycode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/log4js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/mobx/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/moment/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/mqtt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/redux/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/reselect/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/rxjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/should/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/sift/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/soap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/sugar/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/tslint/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/vuex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/xlsx/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/xpath/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings", 3 | "compilerOptions": { 4 | "sourceMap": true, 5 | "outDir": "../dist" 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /userTests/bignumber.js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/eventemitter2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/eventemitter3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/immutable/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/jimp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": ["node"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/jsonschema/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/localforage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/portfinder/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/protobufjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/discord.js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": ["node"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/literal-no-oom/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["esnext", "dom"], 5 | "types": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/octokit-rest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": ["node"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /userTests/antd/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": [], 6 | "allowSyntheticDefaultImports": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | }, 5 | "references": [ 6 | { "path": "src" }, 7 | { "path": "test" }, 8 | ], 9 | "files": [] 10 | } 11 | -------------------------------------------------------------------------------- /userTests/TypeScript-React-Native-Starter/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloneUrl": "https://github.com/Microsoft/TypeScript-React-Native-Starter.git", 3 | "types": ["jest"], 4 | "path": "TypeScript-React-Native-Starter/ExampleProject" 5 | } 6 | -------------------------------------------------------------------------------- /userTests/ajv/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ajv-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "ajv": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/mobx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mobx-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "mobx": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/rxjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rxjs-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "rxjs": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/sift/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sift-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "sift": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/xlsx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xlsx-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "xlsx": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/axios/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "axios-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "axios": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "redux": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/rxjs-src/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf rxjs 4 | git clone --depth 1 https://github.com/ReactiveX/rxjs rxjs 5 | START=$(pwd) 6 | cd $TS 7 | npm link 8 | cd $START/rxjs 9 | npm install 10 | npm link typescript 11 | npm run compile 12 | -------------------------------------------------------------------------------- /userTests/sugar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sugar-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "sugar": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/xpath/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xpath-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "xpath": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/log4js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "log4js-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "log4js": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/moment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moment-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "moment": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/postcss/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "postcss": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/should/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "should-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "should": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/zone.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zone.js-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "zone.js": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "electron": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/firebase/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firebase-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "firebase": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/immutable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "immutable-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "immutable": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/isobject/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isobject-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "isobject": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/reselect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reselect-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "reselect": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/jsonschema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonschema-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "jsonschema": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/portfinder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "portfinder-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "portfinder": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/protobufjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protobufjs-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "protobufjs": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/bignumber.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bignumber.js-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "bignumber.js": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/localforage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "localforage-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "localforage": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/postcss/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "esnext", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "lib": ["esnext", "dom"], 8 | "types": [] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "node16", 4 | "target": "ES2022", 5 | "composite": true, 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "useUnknownInCatchVariables": false, 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /userTests/eventemitter2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eventemitter2-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "eventemitter2": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /userTests/eventemitter3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eventemitter3-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "eventemitter3": "latest" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | testMatch: ["/test/*.test.ts"], 5 | globals: { 6 | "ts-jest": { 7 | tsconfig: "/test/tsconfig.json", 8 | diagnostics: false 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /userTests/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@babel/types": "latest", 10 | "vue": "latest" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /userTests/zone.js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "lib": ["esnext", "dom"], 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "index.ts", 9 | "node_modules/zone.js/dist/zone.js.d.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /userTests/antd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antd-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@types/react": "^16.0.18", 10 | "antd": "latest" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /userTests/keycode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keycode-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@types/node": "^8.0.47", 10 | "keycode": "latest" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/markdownUtils.ts: -------------------------------------------------------------------------------- 1 | export function asMarkdownInlineCode(s: string) { 2 | let backticks = "`"; 3 | let space = ""; 4 | while (s.includes(backticks)) { 5 | backticks += "`"; 6 | space = " " 7 | } 8 | return `${backticks}${space}${s}${space}${backticks}`; 9 | } 10 | -------------------------------------------------------------------------------- /userTests/discord.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discord.js-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@types/node": "^17.0.21", 10 | "discord.js": "latest" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /testResources/scriptPrettier/build.sh: -------------------------------------------------------------------------------- 1 | npm i -g yarn 2 | rm -rf prettier 3 | git clone --depth 1 https://github.com/prettier/prettier.git prettier 4 | START=$(pwd) 5 | cd $TS 6 | rm ~/.config/yarn/link/typescript 7 | yarn link 8 | cd $START/prettier 9 | yarn link typescript 10 | yarn 11 | yarn lint:typecheck 12 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.settings", 3 | "compilerOptions": { 4 | "outDir": "dist", // Output files are ignored, but them in a folder for tidiness 5 | "emitDeclarationOnly": true, 6 | }, 7 | "references": [ 8 | { "path": "../src" }, 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /userTests/vuex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuex-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@babel/types": "latest", 10 | "vue": "latest", 11 | "vuex": "latest" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /userTests/formik/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "strict": true, 5 | "esModuleInterop": true, 6 | "noEmit": true, 7 | "types": [], 8 | "lib": ["esnext", "dom"], 9 | }, 10 | "files": [ 11 | "index.tsx" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /userTests/mqtt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mqtt-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@types/node": "latest", 10 | "@types/ws": "latest", 11 | "mqtt": "latest" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /userTests/renovate/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm i -g yarn 4 | rm -rf renovate 5 | git clone --depth 1 https://github.com/renovatebot/renovate.git renovate 6 | START=$(pwd) 7 | cd $TS 8 | rm ~/.config/yarn/link/typescript 9 | yarn link 10 | cd $START/renovate 11 | yarn link typescript 12 | yarn type-check 13 | -------------------------------------------------------------------------------- /userTests/acorn/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acorn-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "acorn": "^5.5.3" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/async/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "async": "^2.6.0" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/clone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clone-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "clone": "^2.1.1" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/debug/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "debug-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "debug": "^3.1.0" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/jimp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jimp-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "jimp": "latest" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/soap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "soap-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@types/bluebird": "^3.5.17", 10 | "@types/sax": "latest", 11 | "soap": "latest" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /userTests/util/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "util-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "util": "^0.10.3" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/arktype/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | npm i -g pnpm 6 | rm -rf arktype 7 | git clone --depth 1 https://github.com/arktypeio/arktype.git arktype 8 | cd arktype 9 | 10 | npx json -I -f package.json -e "this.resolutions = { ...this.resolutions, typescript: 'file:$TS' } " 11 | pnpm i 12 | pnpm tsc 13 | -------------------------------------------------------------------------------- /userTests/assert/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assert-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "assert": "^1.4.1" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/lodash/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lodash-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "lodash": "^4.17.5" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/npmlog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npmlog-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "npmlog": "^4.1.2" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/prettier/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm i -g yarn 4 | rm -rf prettier 5 | git clone --depth 1 https://github.com/prettier/prettier.git prettier 6 | START=$(pwd) 7 | cd $TS 8 | rm ~/.config/yarn/link/typescript 9 | yarn link 10 | cd $START/prettier 11 | yarn link typescript 12 | yarn 13 | yarn lint:typecheck 14 | -------------------------------------------------------------------------------- /userTests/bcryptjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bcryptjs-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "bcryptjs": "^2.4.3" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/bluebird/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bluebird-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "bluebird": "^3.5.1" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/minimatch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minimatch-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "minimatch": "^3.0.4" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/uglify-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uglify-js-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "uglify-js": "^3.3.13" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/typescript-eslint-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-eslint-parser-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@typescript-eslint/parser": "latest", 10 | "typescript": "latest" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /userTests/clear-require/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clear-require-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "clear-require": "^2.0.0" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/octokit-rest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "octokit-rest-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@octokit/rest": "latest" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/graceful-fs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graceful-fs-framework-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "graceful-fs": "^4.1.11" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/adonis-framework/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adonis-framework-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "adonis-framework": "^3.0.14" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/ts-toolbelt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-toolbelt-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "license": "Apache-2.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/pirix-gh/ts-toolbelt" 10 | }, 11 | "dependencies": { 12 | "ts-toolbelt": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/url-search-params/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "url-search-params-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "url-search-params": "^0.10.0" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/content-disposition/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "content-disposition-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "content-disposition": "^0.5.2" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/enhanced-resolve/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enhanced-resolve-framework-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "enhanced-resolve": "^4.0.0" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/follow-redirects/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "follow-redirects-framework-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "follow-redirects": "^1.4.1" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "latest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /userTests/vue-next/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm install -g pnpm 4 | rm -rf vue-next 5 | git clone --depth 1 https://github.com/vuejs/core 6 | 7 | START=$(pwd) 8 | cd $TS 9 | pnpm link --global # TODO: This doesn't work, or else I need to rebuild 10 | cd $START/core 11 | pnpm link typescript 12 | pnpm install 13 | npm run build --production -- --types 14 | -------------------------------------------------------------------------------- /userTests/angular/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm i -g yarn --force 4 | rm -rf angular 5 | git clone --depth 1 https://github.com/angular/angular angular 6 | START=$(pwd) 7 | cd $TS 8 | rm ~/.config/yarn/link/typescript 9 | yarn link 10 | 11 | cd $START/angular 12 | yarn link typescript 13 | yarn install --ignore-scripts 14 | 15 | yarn tsc -p ./packages/tsconfig.json 16 | -------------------------------------------------------------------------------- /userTests/test262/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | 7 | "strict": true, 8 | "types": [], 9 | "lib": ["ESNext"], 10 | 11 | "noEmit": true, 12 | "allowJs": true 13 | }, 14 | "include": ["test262/test/**/*.js"] 15 | } 16 | -------------------------------------------------------------------------------- /userTests/pyright/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf pyright 4 | git clone --depth 1 https://github.com/microsoft/pyright.git pyright 5 | START=$(pwd) 6 | cd $TS 7 | npm link 8 | cd $START/pyright 9 | npm i 10 | npm link typescript 11 | 12 | 13 | npx lerna exec --stream --concurrency 1 -- npm link typescript 14 | npx lerna exec --stream --concurrency 1 --no-bail -- tsc --noEmit 15 | -------------------------------------------------------------------------------- /userTests/vscode/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf vscode 4 | git clone --depth 1 https://github.com/microsoft/vscode.git vscode 5 | START=$(pwd) 6 | cd $TS 7 | npm link 8 | 9 | cd $START/vscode/build 10 | npm link typescript 11 | cd $START/vscode/extensions 12 | npm add rimraf 13 | npm link typescript 14 | cd $START/vscode 15 | npm link typescript 16 | npm install 17 | npm run compile 18 | -------------------------------------------------------------------------------- /src/utils/exerciseServerConstants.ts: -------------------------------------------------------------------------------- 1 | export const EXIT_BAD_ARGS = 1; 2 | export const EXIT_UNHANDLED_EXCEPTION = 2; 3 | export const EXIT_SERVER_EXIT_FAILED = 3; 4 | export const EXIT_SERVER_CRASH = 4; // The server process actually exited 5 | export const EXIT_SERVER_ERROR = 5; // The server process sent an error response 6 | export const EXIT_LANGUAGE_SERVICE_DISABLED = 6; 7 | export const EXIT_SERVER_COMMUNICATION_ERROR = 7; -------------------------------------------------------------------------------- /userTests/axios-src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": ["node"], 11 | "lib": ["esnext", "dom"], 12 | }, 13 | "include": ["axios-src/lib"] 14 | } 15 | -------------------------------------------------------------------------------- /userTests/formik/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formik", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "formik": "latest", 13 | "@types/react": "latest", 14 | "@types/prop-types": "latest" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /userTests/async/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": ["node"], 11 | "lib": ["esnext", "dom"], 12 | }, 13 | "include": ["index.ts", "node_modules/async"] 14 | } 15 | -------------------------------------------------------------------------------- /userTests/debug/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": ["node"], 11 | "lib": ["esnext", "dom"], 12 | }, 13 | "include": ["index.ts", "node_modules/debug"] 14 | } 15 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | Because this is intended for use by the Typescript team, you may also want to contact one of us directly. 10 | Try the Typescript community discord or twitter. 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /userTests/typescript-eslint/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | npm i -g yarn 6 | rm -rf typescript-eslint 7 | git clone --depth 1 https://github.com/typescript-eslint/typescript-eslint.git typescript-eslint 8 | cd typescript-eslint 9 | 10 | export NX_NO_CLOUD=true 11 | 12 | time yarn 13 | 14 | export SKIP_POSTINSTALL=true 15 | npx json -I -f package.json -e "this.resolutions.typescript = 'file:$TS'" 16 | 17 | time yarn 18 | 19 | yarn tsc --version 20 | 21 | yarn typecheck 22 | -------------------------------------------------------------------------------- /userTests/acorn/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/acorn", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/assert/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/assert", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/clone/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/clone", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/npmlog/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/npmlog", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/util/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/util", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/npm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "commonjs", 5 | "resolveJsonModule": true, 6 | "target": "es2019", 7 | "noImplicitAny": false, 8 | "noImplicitThis": true, 9 | "strict": true, 10 | "maxNodeModuleJsDepth": 0, 11 | "noEmit": true, 12 | "allowJs": true, 13 | "checkJs": true, 14 | "types": ["node"], 15 | "lib": ["esnext"] 16 | }, 17 | "include": ["npm/lib"] 18 | } 19 | -------------------------------------------------------------------------------- /userTests/graceful-fs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/graceful-fs", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/grunt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "commonjs", 5 | "resolveJsonModule": true, 6 | "target": "es2019", 7 | "noImplicitAny": false, 8 | "noImplicitThis": true, 9 | "strict": true, 10 | "maxNodeModuleJsDepth": 0, 11 | "noEmit": true, 12 | "allowJs": true, 13 | "checkJs": true, 14 | "types": ["node"], 15 | "lib": ["esnext"] 16 | }, 17 | "include": ["grunt/lib"] 18 | } 19 | -------------------------------------------------------------------------------- /userTests/minimatch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/minimatch", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/uglify-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/uglify-js", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/bluebird/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/bluebird/js/release", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/clear-require/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "lib": [ 11 | "esnext", 12 | "dom" 13 | ], 14 | "types": [ 15 | "node" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/clear-require", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/adonis-framework/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/adonis-framework", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/follow-redirects/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/follow-redirects", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /src/utils/hashStackTrace.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "crypto"; 2 | 3 | export function getHash(methods: string[]): string { 4 | const lines = methods.join("\n"); 5 | return createHash("md5").update(lines).digest("hex"); 6 | } 7 | 8 | export function getHashForStack(stack: string): string { 9 | const stackLines = stack.split(/\r?\n/); 10 | 11 | return getHash(stackLines); 12 | } 13 | 14 | export function getErrorMessageFromStack(stack: string): string { 15 | const stackLines = stack.split(/\r?\n/, 2); 16 | 17 | return stackLines[1]; 18 | } -------------------------------------------------------------------------------- /azure-pipelines-gitTests-tsserver-js.yml: -------------------------------------------------------------------------------- 1 | # Disabled during Corsa migration 2 | # schedules: 3 | # - cron: "0 20 * * Sun" # time is in UTC 4 | # displayName: Sunday overnight run 5 | # always: true 6 | # branches: 7 | # include: 8 | # - main 9 | 10 | pr: none 11 | trigger: none 12 | 13 | pool: 14 | name: TypeScript-1ES-Large 15 | demands: 16 | - ImageOverride -equals azure-linux-3 17 | 18 | extends: 19 | template: azure-pipelines-gitTests-template.yml 20 | parameters: 21 | ENTRYPOINT: tsserver 22 | LANGUAGE: JavaScript 23 | -------------------------------------------------------------------------------- /azure-pipelines-gitTests-tsserver-ts.yml: -------------------------------------------------------------------------------- 1 | # Disabled during Corsa migration 2 | # schedules: 3 | # - cron: "0 19 * * Sun" # time is in UTC 4 | # displayName: Sunday overnight run 5 | # always: true 6 | # branches: 7 | # include: 8 | # - main 9 | 10 | pr: none 11 | trigger: none 12 | 13 | pool: 14 | name: TypeScript-1ES-Large 15 | demands: 16 | - ImageOverride -equals azure-linux-3 17 | 18 | extends: 19 | template: azure-pipelines-gitTests-template.yml 20 | parameters: 21 | ENTRYPOINT: tsserver 22 | LANGUAGE: TypeScript 23 | -------------------------------------------------------------------------------- /userTests/content-disposition/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/content-disposition", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/url-search-params/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/url-search-params/build/url-search-params.node.js", 20 | "index.ts" 21 | ] 22 | } -------------------------------------------------------------------------------- /userTests/lodash/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/lodash", 20 | "index.ts" 21 | ], 22 | "exclude": [ 23 | "node_modules/lodash/lodash.js" 24 | ] 25 | } -------------------------------------------------------------------------------- /azure-pipelines-compliance.yml: -------------------------------------------------------------------------------- 1 | pr: 2 | - main 3 | 4 | pool: 5 | name: VSEngSS-MicroBuild2019-1ES 6 | 7 | variables: 8 | TeamName: TypeScript 9 | 10 | steps: 11 | - task: NuGetToolInstaller@1 12 | inputs: 13 | versionSpec: '5.x' 14 | - task: CredScan@3 15 | - task: PoliCheck@2 16 | - task: AntiMalware@4 17 | - task: PublishSecurityAnalysisLogs@3 18 | - task: PostAnalysis@2 19 | 20 | - task: UseNode@1 21 | inputs: 22 | version: '20.x' 23 | displayName: 'Install Node.js' 24 | - script: | 25 | npm ci 26 | npm run build 27 | displayName: 'npm install and build' 28 | 29 | - task: MicroBuildCleanup@1 30 | -------------------------------------------------------------------------------- /userTests/enhanced-resolve/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ], 17 | "module": "CommonJS", 18 | "target": "esnext" 19 | }, 20 | "include": [ 21 | "node_modules/enhanced-resolve", 22 | "index.ts" 23 | ] 24 | } -------------------------------------------------------------------------------- /userTests/bcryptjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": [ 11 | "node" 12 | ], 13 | "lib": [ 14 | "esnext", 15 | "dom" 16 | ] 17 | }, 18 | "include": [ 19 | "node_modules/bcryptjs/scripts", 20 | "node_modules/bcryptjs/src", 21 | "node_modules/bcryptjs/tests", 22 | "index.ts" 23 | ] 24 | } -------------------------------------------------------------------------------- /userTests/create-react-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": false, 4 | "noImplicitThis": false, 5 | "maxNodeModuleJsDepth": 0, 6 | "strict": true, 7 | "noEmit": true, 8 | "allowJs": true, 9 | "checkJs": true, 10 | "types": ["node"], 11 | "lib": ["esnext", "dom"], 12 | "baseUrl": "./", 13 | "paths": { 14 | "chalk": ["chalk-override"] 15 | } 16 | }, 17 | "include": ["create-react-app"], 18 | "exclude": ["create-react-app/packages/react-error-overlay", 19 | "create-react-app/packages/react-scripts"] 20 | } 21 | -------------------------------------------------------------------------------- /src/listUserTestRepos.ts: -------------------------------------------------------------------------------- 1 | import fs = require("fs"); 2 | import path = require("path"); 3 | import { reportError } from "./main"; 4 | import { getUserTestsRepos } from "./utils/userTestUtils"; 5 | 6 | const { argv } = process; 7 | 8 | if (argv.length !== 4) { 9 | console.error(`Usage: ${path.basename(argv[0])} ${path.basename(argv[1])} `); 10 | process.exit(-1); 11 | } 12 | 13 | const userTestsDir = argv[2]; 14 | const outputPath = argv[3]; 15 | 16 | try { 17 | const repos = getUserTestsRepos(userTestsDir); 18 | fs.writeFileSync(outputPath, JSON.stringify(repos), { encoding: "utf-8" }); 19 | } 20 | catch (err) { 21 | reportError(err, "Unhandled exception"); 22 | process.exit(1); 23 | } -------------------------------------------------------------------------------- /userTests/chrome-devtools-frontend-next/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf depot_tools 4 | git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git depot_tools 5 | PATH=depot_tools:$PATH 6 | rm -rf devtools 7 | mkdir devtools 8 | cd devtools 9 | fetch devtools-frontend 10 | cd devtools-frontend 11 | gn gen out/Default 12 | rm -rf node_modules/typescript 13 | ln -s $TS node_modules/typescript 14 | 15 | # We don't want to show the ordering of which tasks ran in Ninja, as that is non-deterministic. 16 | # Instead, only show the errors in the log, from the first occurrence of a FAILED task. 17 | # If the task passes, then there is no log written. 18 | autoninja -C out/Default >error.log || tail -n +$(sed -n '/FAILED/=' error.log) error.log 19 | -------------------------------------------------------------------------------- /userTests/tslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tslint-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "author": "", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "tslint": "latest", 10 | "typescript": "latest" 11 | }, 12 | "devDependencies": { 13 | "@types/babel-code-frame": "latest", 14 | "@types/chai": "latest", 15 | "@types/chalk": "latest", 16 | "@types/commander": "latest", 17 | "@types/diff": "latest", 18 | "@types/glob": "latest", 19 | "@types/js-yaml": "latest", 20 | "@types/minimatch": "latest", 21 | "@types/mocha": "latest", 22 | "@types/node": "latest", 23 | "@types/resolve": "latest", 24 | "@types/rimraf": "latest", 25 | "@types/semver": "latest" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /userTests/typescript-eslint-parser/index.ts: -------------------------------------------------------------------------------- 1 | import * as parser from "@typescript-eslint/parser"; 2 | import * as path from "path"; 3 | 4 | const code = ` 5 | import { something } from '__PLACEHOLDER__'; 6 | 7 | something(); 8 | `; 9 | 10 | const fixturesDirectory = path.resolve(__dirname, "fixtures"); 11 | const projectDirectory = path.resolve(fixturesDirectory, "project"); 12 | 13 | parser 14 | .parseAndGenerateServices(code, { 15 | comment: true, 16 | filePath: path.resolve(projectDirectory, "file.ts"), 17 | loc: true, 18 | moduleResolver: path.resolve(fixturesDirectory, "./moduleResolver.js"), 19 | project: "./tsconfig.json", 20 | range: true, 21 | tokens: true, 22 | tsconfigRootDir: projectDirectory, 23 | }) 24 | .services.program.getSemanticDiagnostics(); 25 | -------------------------------------------------------------------------------- /test/projectGraph.test.ts: -------------------------------------------------------------------------------- 1 | import { getProjectsToBuild } from '../src/utils/projectGraph' 2 | 3 | describe("getProjectsToBuild", () => { 4 | it("gets a simple project", async () => { 5 | const result = await getProjectsToBuild("./testResources/simpleProject") 6 | expect(result.simpleProjects[0].path.endsWith("testResources/simpleProject/tsconfig.json")) 7 | delete (result.simpleProjects[0] as any).path 8 | expect(result).toEqual({ 9 | simpleProjects: [{ 10 | hasParseError: false, 11 | hasExtensionError: false, 12 | hasReferenceError: false, 13 | isComposite: false, 14 | references: [], 15 | referencedBy: [], 16 | extends: [], 17 | extendedBy: [], 18 | }], 19 | rootCompositeProjects: [], 20 | hasError: false, 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /userTests/ts-toolbelt/index.ts: -------------------------------------------------------------------------------- 1 | // ! this library is mostly used with ramda 2 | 3 | import {I, T, Test} from "ts-toolbelt"; 4 | 5 | const {check, checks} = Test; 6 | 7 | // iterates over `T` and returns the `Iteration` position when finished 8 | type StdRecursiveIteration> = { 9 | 0: StdRecursiveIteration>; 10 | 1: I.Pos; 11 | }[ 12 | I.Pos extends T.Length // this form of recursion is preferred 13 | ? 1 // because it will let the user know if 14 | : 0 // the instantiation depth has been hit 15 | ]; 16 | 17 | checks([ 18 | check, 40, Test.Pass>(), // max length is 40 24 | ]); 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | # Ensure scripts are run with pipefail. See: 15 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference 16 | defaults: 17 | run: 18 | shell: bash 19 | 20 | jobs: 21 | test: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: actions/setup-node@v3 27 | with: 28 | node-version: 20 29 | - run: npm ci 30 | 31 | - name: Test 32 | run: npm test 33 | 34 | - name: Check scripts are executable 35 | run: | 36 | for i in userTests/*/*.sh; do 37 | if ! [[ -x "$i" ]]; then 38 | echo "File $i is not executable" 39 | exit 1 40 | fi 41 | done 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to 4 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 5 | and actually do, grant us the rights to use your contribution. For details, visit 6 | https://cla.microsoft.com. 7 | 8 | When you submit a pull request, a CLA-bot will automatically determine whether you need 9 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 10 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 14 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 15 | -------------------------------------------------------------------------------- /userTests/office-ui-fabric/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm i -g yarn 4 | rm -rf office-ui-fabric 5 | CI=true 6 | TF_BUILD=true 7 | # Download repo version, sets up better layer caching for the following clone 8 | curl https://api.github.com/repositories/60537144/git/ref/heads/master -o version.json 9 | git clone --depth 1 https://github.com/OfficeDev/office-ui-fabric-react.git office-ui-fabric-react 10 | START=$(pwd) 11 | cd $TS 12 | rm ~/.config/yarn/link/typescript 13 | yarn link 14 | cd $START/office-ui-fabric-react 15 | yarn link typescript 16 | yarn 17 | # Sync up all TS versions used internally to the new one (we use `npm` because `yarn` chokes on tarballs installed 18 | # into multiple places in a workspace in a short timeframe (some kind of data race)) 19 | # sed -i -e 's/"resolutions": {/"resolutions": { "\*\*\/typescript": "file:\/typescript\.tgz",/g' package.json 20 | # npx yarn 21 | 22 | npx lerna exec --stream --concurrency 1 --loglevel error --bail=false -- yarn run just ts 23 | -------------------------------------------------------------------------------- /src/checkGithubRepos.ts: -------------------------------------------------------------------------------- 1 | import path = require("path"); 2 | import { mainAsync, reportError, TsEntrypoint } from "./main"; 3 | 4 | const { argv } = process; 5 | 6 | if (argv.length !== 11) { 7 | console.error(`Usage: ${path.basename(argv[0])} ${path.basename(argv[1])} `); 8 | process.exit(-1); 9 | } 10 | 11 | const [,, entrypoint, oldTsNpmVersion, newTsNpmVersion, repoListPath, workerCount, workerNumber, resultDirName, diagnosticOutput, prngSeed] = argv; 12 | 13 | mainAsync({ 14 | testType: "github", 15 | tmpfs: true, 16 | entrypoint: entrypoint as TsEntrypoint, 17 | diagnosticOutput: diagnosticOutput.toLowerCase() === "true", 18 | buildWithNewWhenOldFails: false, 19 | repoListPath, 20 | workerCount: +workerCount, 21 | workerNumber: +workerNumber, 22 | oldTsNpmVersion, 23 | newTsNpmVersion, 24 | resultDirName, 25 | prngSeed: prngSeed.toLowerCase() === "n/a" ? undefined : prngSeed, 26 | }).catch(err => { 27 | reportError(err, "Unhandled exception"); 28 | process.exit(1); 29 | }); 30 | -------------------------------------------------------------------------------- /userTests/postcss/index.ts: -------------------------------------------------------------------------------- 1 | import postcss from "postcss"; 2 | import type { PluginCreator } from 'postcss'; 3 | 4 | // Plugin options 5 | type pluginOptions = { anOption?: string }; 6 | 7 | // A plugin class 8 | const pluginCreator: PluginCreator = (opts?: pluginOptions) => { 9 | return { 10 | postcssPlugin: 'postcss-base-plugin', 11 | Declaration(decl) { 12 | decl.value = opts?.anOption ?? 'replacement'; 13 | }, 14 | }; 15 | }; 16 | 17 | // Mark the plugin class as a PostCSS plugin 18 | pluginCreator.postcss = true; 19 | 20 | // Create a list of plugins 21 | // Passing both: 22 | // - un-initialized plugin class 23 | // - a plugin instance 24 | const processor = postcss([ 25 | pluginCreator, 26 | pluginCreator({anOption: 'value'}), 27 | ]); 28 | 29 | // Some CSS string 30 | const css = ` 31 | :root { 32 | some: property; 33 | } 34 | `; 35 | 36 | // Process the CSS and await the result 37 | const result = await processor.process(css, { 38 | from: 'from-somewhere', 39 | to: 'to-somewhere', 40 | }); 41 | 42 | // Check for messages 43 | result.messages.forEach((message) => { 44 | console.log(message.type); 45 | }); 46 | 47 | // Log the processed CSS 48 | console.log(result.css); 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 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 | -------------------------------------------------------------------------------- /src/checkUserTestRepos.ts: -------------------------------------------------------------------------------- 1 | import path = require("path"); 2 | import { mainAsync, reportError, TsEntrypoint } from "./main"; 3 | 4 | const { argv } = process; 5 | 6 | if (argv.length !== 13) { 7 | console.error(`Usage: ${path.basename(argv[0])} ${path.basename(argv[1])} `); 8 | process.exit(-1); 9 | } 10 | 11 | const [,, entrypoint, oldTsRepoUrl, oldHeadRef, prNumber, buildWithNewWhenOldFails, repoListPath, workerCount, workerNumber, resultDirName, diagnosticOutput, prngSeed] = argv; 12 | 13 | mainAsync({ 14 | testType: "user", 15 | tmpfs: true, 16 | entrypoint: entrypoint as TsEntrypoint, 17 | oldTsRepoUrl, 18 | oldHeadRef, 19 | prNumber: +prNumber, 20 | buildWithNewWhenOldFails: buildWithNewWhenOldFails.toLowerCase() !== "true", 21 | repoListPath, 22 | workerCount: +workerCount, 23 | workerNumber: +workerNumber, 24 | resultDirName, 25 | diagnosticOutput: diagnosticOutput.toLowerCase() === "true", 26 | prngSeed: prngSeed.toLowerCase() === "n/a" ? undefined : prngSeed, 27 | }).catch(err => { 28 | reportError(err, "Unhandled exception"); 29 | process.exit(1); 30 | }); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-error-deltas", 3 | "version": "0.3.0", 4 | "private": true, 5 | "description": "Script for comparing the errors from two builds of the TypeScript compiler", 6 | "main": "dist/main.js", 7 | "typings": "dist/main.d.ts", 8 | "scripts": { 9 | "build": "tsc -b .", 10 | "test": "npm run build && jest" 11 | }, 12 | "author": "Microsoft", 13 | "license": "MIT", 14 | "engines": { 15 | "node": ">=18" 16 | }, 17 | "dependencies": { 18 | "@octokit/rest": "^20.0.2", 19 | "@typescript/github-link": "^0.2.2", 20 | "@typescript/server-harness": "^0.3.5", 21 | "@typescript/server-replay": "^0.2.13", 22 | "glob": "^10.5.0", 23 | "js-yaml": "^4.1.0", 24 | "json5": "^2.2.3", 25 | "markdown-escape": "^2.0.0", 26 | "random-seed": "^0.3.0", 27 | "simple-git": "^3.22.0" 28 | }, 29 | "devDependencies": { 30 | "@types/jest": "^29.5.12", 31 | "@types/js-yaml": "^4.0.9", 32 | "@types/markdown-escape": "^1.1.3", 33 | "@types/node": "^18.19.23", 34 | "@types/random-seed": "^0.3.5", 35 | "jest": "^29.7.0", 36 | "ts-jest": "^29.1.2", 37 | "typescript": "^5.4.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /userTests/azure-sdk/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | npm install -g @microsoft/rush 5 | rm -rf azure-sdk 6 | git clone --depth 1 https://github.com/Azure/azure-sdk-for-js.git azure-sdk 7 | cd azure-sdk 8 | START=$(pwd) 9 | rush update 10 | cd sdk/core/core-util 11 | # Sync up all TS versions used internally so they're all linked from a known location 12 | LOCAL_TS=$(node -e 'console.log(JSON.parse(fs.readFileSync("node_modules/typescript/package.json", "utf8")).version)') 13 | rush add -p "typescript@$LOCAL_TS" --dev -m 14 | # -nervous laugh- 15 | # Relink installed TSes to built TS 16 | cd $START/common/temp/node_modules/.pnpm/typescript@$LOCAL_TS/node_modules 17 | rm -rf typescript 18 | ln -s $TS ./typescript 19 | cd $START 20 | 21 | # Running everything takes a long time; just build the top 20 client packages by downloads. 22 | rush rebuild \ 23 | --to @azure/identity \ 24 | --to @azure/storage-blob \ 25 | --to @azure/keyvault-keys \ 26 | --to @azure/opentelemetry-instrumentation-azure-sdk \ 27 | --to @azure/cosmos \ 28 | --to @azure/keyvault-secrets \ 29 | --to @azure/service-bus \ 30 | --to @azure/openai \ 31 | --to @azure/app-configuration \ 32 | --to @azure/storage-queue \ 33 | --to @azure/storage-file-share \ 34 | --to @azure/event-hubs \ 35 | --to @azure/communication-common \ 36 | --to @azure/data-tables \ 37 | --to @azure/storage-file-datalake \ 38 | --to @azure/search-documents \ 39 | --to @azure/web-pubsub-client \ 40 | --to @azure/maps-common \ 41 | --to @azure/ai-form-recognizer \ 42 | --to @azure/communication-email 43 | -------------------------------------------------------------------------------- /src/utils/userTestUtils.ts: -------------------------------------------------------------------------------- 1 | import { execAsync } from "./execUtils"; 2 | import type { Repo } from "./gitUtils"; 3 | import * as fs from "fs"; 4 | import * as path from "path"; 5 | 6 | interface UserConfig { 7 | types: string[]; 8 | cloneUrl: string; 9 | branch?: string; 10 | path?: string; 11 | } 12 | 13 | export function getUserTestsRepos(testDir: string): Repo[] { 14 | const repoDirectories = fs.readdirSync(`${testDir}`, { withFileTypes: true }) 15 | .filter(value => value.isDirectory()) 16 | .map(value => value.name); 17 | 18 | const repos: Repo[] = []; 19 | for (let directory of repoDirectories) { 20 | const testFile = path.join(testDir, directory, "test.json"); 21 | if (fs.existsSync(testFile)) { 22 | const config = JSON.parse(fs.readFileSync(testFile, { encoding: "utf8" })) as UserConfig; 23 | repos.push({ 24 | name: directory, 25 | url: config.cloneUrl, 26 | types: config.types, 27 | branch: config.branch, 28 | }); 29 | } 30 | else if (fs.existsSync(path.join(testDir, directory, "package.json")) || fs.existsSync(path.join(testDir, directory, "build.sh"))) { 31 | repos.push({ 32 | name: directory, 33 | }); 34 | } 35 | } 36 | 37 | return repos; 38 | } 39 | 40 | export async function copyUserRepo(parentDir: string, testDir: string, repo: Repo,) { 41 | const repoDir = path.join(parentDir, repo.name); 42 | await execAsync(parentDir, `mkdir ${repoDir}`); 43 | await execAsync(repoDir, `cp -R ${path.join(testDir, repo.name)}/* .`); 44 | } 45 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Create artifacts folder", 8 | "type": "shell", 9 | "command": "rm -rf artifacts; mkdir artifacts", 10 | "windows": { 11 | "command": "if (Test-Path artifacts) { Remove-Item -Force -Recurse artifacts }; New-Item -Force -ItemType directory artifacts" 12 | } 13 | }, 14 | { 15 | "label": "Clean ts_downloads and artifacts folders", 16 | "type": "shell", 17 | "command": "rm -d -rf ts_downloads; ls ./artifacts | grep -xv repos.json | xargs -r rm", 18 | "windows": { 19 | "command": "if (Test-Path ts_downloads) { Remove-Item -Force -Recurse ts_downloads }; Remove-Item artifacts/* -Exclude repos.json" 20 | } 21 | }, 22 | { 23 | "type": "npm", 24 | "script": "build", 25 | "group": { 26 | "kind": "build" 27 | }, 28 | "problemMatcher": [], 29 | "label": "npm: build", 30 | "detail": "tsc -b ." 31 | }, 32 | { 33 | "label": "tsc: watch ./src", 34 | "type": "shell", 35 | "command": "node", 36 | "args": ["${workspaceFolder}/node_modules/typescript/lib/tsc.js", "--build", ".", "--watch"], 37 | "group": "build", 38 | "isBackground": true, 39 | "problemMatcher": [ 40 | "$tsc-watch" 41 | ] 42 | }, 43 | { 44 | "label": "Clean all", 45 | "type": "shell", 46 | "command": "rm -d -rf artifacts ts_downloads typescript-*", 47 | "windows": { 48 | "command": "Remove-Item -Force -Recurse artifacts, ts_downloads, typescript-*" 49 | } 50 | }, 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /src/listTopRepos.ts: -------------------------------------------------------------------------------- 1 | import fs = require("fs"); 2 | import path = require("path"); 3 | import { getPopularRepos } from "./utils/gitUtils"; 4 | import { reportError } from "./main"; 5 | 6 | const { argv } = process; 7 | 8 | if (argv.length !== 6) { 9 | console.error(`Usage: ${path.basename(argv[0])} ${path.basename(argv[1])} `); 10 | process.exit(-1); 11 | } 12 | 13 | const language = argv[2]; 14 | const repoCount = +argv[3]; 15 | const repoStartIndex = +argv[4]; 16 | const outputPath = argv[5]; 17 | 18 | // If you think we need coverage of one of these, consider adding a user test with custom build steps 19 | const skipRepos = [ 20 | "https://github.com/microsoft/TypeScript", // Test files expected to have errors 21 | "https://github.com/DefinitelyTyped/DefinitelyTyped", // Test files expected to have errors 22 | "https://github.com/storybookjs/storybook", // Too big to fit on VM 23 | "https://github.com/microsoft/frontend-bootcamp", // Can't be built twice in a row 24 | "https://github.com/BabylonJS/Babylon.js", // Runs out of space during compile 25 | "https://github.com/eclipse-theia/theia", // Probably same 26 | "https://github.com/wbkd/react-flow", // Probably same 27 | "https://github.com/remix-run/remix", // Too big to fit on VM 28 | "https://github.com/NervJS/taro", // Too big to fit on VM 29 | "https://github.com/TanStack/table", // Too big to fit on VM 30 | "https://github.com/doczjs/docz", // Too big to fit on VM 31 | "https://github.com/NativeScript/NativeScript", // Uses NX package manager 32 | "https://github.com/wulkano/Kap", // Incompatible with Linux 33 | "https://github.com/lit/lit", // Depends on non-public package 34 | "https://github.com/coder/code-server", // Takes ~15 minutes and overlaps heavily with vscode 35 | ]; 36 | 37 | async function mainAsync() { 38 | const repos = await getPopularRepos(language, repoCount, repoStartIndex, skipRepos); 39 | if (repos.length === 0) { 40 | throw new Error("No repos found"); 41 | } 42 | await fs.promises.writeFile(outputPath, JSON.stringify(repos), { encoding: "utf-8" }); 43 | } 44 | 45 | mainAsync().catch(err => { 46 | reportError(err, "Unhandled exception"); 47 | process.exit(1); 48 | }); 49 | -------------------------------------------------------------------------------- /azure-pipelines-gitTests.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: POST_RESULT 3 | displayName: Post GitHub issue with results 4 | type: boolean 5 | default: true 6 | - name: DIAGNOSTIC_OUTPUT 7 | displayName: Log diagnostic data 8 | type: boolean 9 | default: false 10 | - name: REPO_COUNT 11 | displayName: Repo Count 12 | type: number 13 | default: 800 14 | - name: REPO_START_INDEX 15 | displayName: Repo Start Index 16 | type: number 17 | default: 0 18 | - name: OLD_VERSION 19 | displayName: Baseline TypeScript package version 20 | type: string 21 | default: latest 22 | - name: NEW_VERSION 23 | displayName: Candidate TypeScript package version 24 | type: string 25 | default: next 26 | - name: MACHINE_COUNT 27 | displayName: Machine Count 28 | type: number 29 | default: 16 30 | values: 31 | - 1 32 | - 2 33 | - 4 34 | - 8 35 | - 16 36 | - name: ENTRYPOINT 37 | displayName: TypeScript entrypoint 38 | type: string 39 | default: tsc 40 | values: 41 | - tsc 42 | - tsserver 43 | - name: LANGUAGE 44 | displayName: Language of repos on GitHub (tsserver only) 45 | type: string 46 | default: TypeScript 47 | values: 48 | - TypeScript 49 | - JavaScript 50 | - name: PRNG_SEED 51 | displayName: Pseudo-random number generator seed 52 | type: string 53 | default: 'n/a' 54 | 55 | # (ryanca) disabled during 6.0 migration period as this isn't useful 56 | # schedules: 57 | # - cron: "0 17 * * Sun" # time is in UTC 58 | # displayName: Sunday overnight run 59 | # always: true 60 | # branches: 61 | # include: 62 | # - main 63 | 64 | pr: none 65 | trigger: none 66 | 67 | pool: 68 | name: TypeScript-1ES-Large 69 | demands: 70 | - ImageOverride -equals azure-linux-3 71 | 72 | variables: 73 | Codeql.Enabled: false 74 | skipComponentGovernanceDetection: true 75 | 76 | extends: 77 | template: azure-pipelines-gitTests-template.yml 78 | parameters: 79 | POST_RESULT: ${{ parameters.POST_RESULT }} 80 | DIAGNOSTIC_OUTPUT: ${{ parameters.DIAGNOSTIC_OUTPUT }} 81 | REPO_COUNT: ${{ parameters.REPO_COUNT }} 82 | REPO_START_INDEX: ${{ parameters.REPO_START_INDEX }} 83 | OLD_VERSION: ${{ parameters.OLD_VERSION }} 84 | NEW_VERSION: ${{ parameters.NEW_VERSION }} 85 | MACHINE_COUNT: ${{ parameters.MACHINE_COUNT }} 86 | ENTRYPOINT: ${{ parameters.ENTRYPOINT }} 87 | LANGUAGE: ${{ parameters.LANGUAGE }} 88 | PRNG_SEED: ${{ parameters.PRNG_SEED }} 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ts_downloads/ 2 | testDownloads/ 3 | artifacts/ 4 | *.tsbuildinfo 5 | *.d.ts 6 | *.js.map 7 | *.js 8 | /.vscode 9 | /typescript-* 10 | userTests/chrome-devtools-frontend-next/depot_tools 11 | userTests/chrome-devtools-frontend-next/devtools 12 | userTests/office-ui-fabric/version.json 13 | userTests/office-ui-fabric/office-ui-fabric-react 14 | userTests/pyright/pyright 15 | userTests/rxjs-src/rxjs 16 | userTests/vscode/vscode 17 | userTests/vue-next/vue-next 18 | test/scriptPrettier/prettier 19 | 20 | # Logs 21 | logs 22 | *.log 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | lerna-debug.log* 27 | 28 | # Diagnostic reports (https://nodejs.org/api/report.html) 29 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 30 | 31 | # Runtime data 32 | pids 33 | *.pid 34 | *.seed 35 | *.pid.lock 36 | 37 | # Directory for instrumented libs generated by jscoverage/JSCover 38 | lib-cov 39 | 40 | # Coverage directory used by tools like istanbul 41 | coverage 42 | *.lcov 43 | 44 | # nyc test coverage 45 | .nyc_output 46 | 47 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 48 | .grunt 49 | 50 | # Bower dependency directory (https://bower.io/) 51 | bower_components 52 | 53 | # node-waf configuration 54 | .lock-wscript 55 | 56 | # Compiled binary addons (https://nodejs.org/api/addons.html) 57 | build/Release 58 | 59 | # Dependency directories 60 | node_modules/ 61 | jspm_packages/ 62 | 63 | # TypeScript v1 declaration files 64 | typings/ 65 | 66 | # TypeScript cache 67 | *.tsbuildinfo 68 | 69 | # Optional npm cache directory 70 | .npm 71 | 72 | # Optional eslint cache 73 | .eslintcache 74 | 75 | # Microbundle cache 76 | .rpt2_cache/ 77 | .rts2_cache_cjs/ 78 | .rts2_cache_es/ 79 | .rts2_cache_umd/ 80 | 81 | # Optional REPL history 82 | .node_repl_history 83 | 84 | # Output of 'npm pack' 85 | *.tgz 86 | 87 | # Yarn Integrity file 88 | .yarn-integrity 89 | 90 | # dotenv environment variables file 91 | .env 92 | .env.test 93 | 94 | # parcel-bundler cache (https://parceljs.org/) 95 | .cache 96 | 97 | # Next.js build output 98 | .next 99 | 100 | # Nuxt.js build / generate output 101 | .nuxt 102 | dist 103 | 104 | # Gatsby files 105 | .cache/ 106 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 107 | # https://nextjs.org/blog/next-9-1#public-directory-support 108 | # public 109 | 110 | # vuepress build output 111 | .vuepress/dist 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | !userTests/*.js/ 126 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /userTests/formik/index.tsx: -------------------------------------------------------------------------------- 1 | // Render Prop 2 | import React from 'react'; 3 | import { Formik, FormikErrors } from 'formik'; 4 | 5 | type MyData = {email: string, password: string}; 6 | declare function LoginToMyApp(data: MyData): Promise<{user: string}>; 7 | declare function transformMyApiErrors(o: any): FormikErrors; 8 | 9 | const Basic = () => ( 10 |
11 |

My Form

12 |

This can be anywhere in your application

13 | {/* 14 | The benefit of the render prop approach is that you have full access to React's 15 | state, props, and composition model. Thus there is no need to map outer props 16 | to values...you can just set the initial values, and if they depend on props / state 17 | then--boom--you can directly access to props / state. 18 | 19 | The render prop accepts your inner form component, which you can define separately or inline 20 | totally up to you: 21 | - `
...
}>` 22 | - `` 23 | - `{props =>
...
}
` (identical to as render, just written differently) 24 | */} 25 | { 31 | // same as above, but feel free to move this into a class method now. 32 | let errors: FormikErrors = {} as FormikErrors; // FormikErrors isn't optionalized, so in strict null checks this needs a cast 33 | if (!values.email) { 34 | errors.email = 'Required'; 35 | } else if ( 36 | !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email) 37 | ) { 38 | errors.email = 'Invalid email address'; 39 | } 40 | return errors; 41 | }} 42 | onSubmit={( 43 | values, 44 | { setSubmitting, setErrors /* setValues and other goodies */ } 45 | ) => { 46 | LoginToMyApp(values).then( 47 | user => { 48 | setSubmitting(false); 49 | // do whatevs... 50 | // props.updateUser(user) 51 | }, 52 | errors => { 53 | setSubmitting(false); 54 | // Maybe transform your API's errors into the same shape as Formik's 55 | setErrors(transformMyApiErrors(errors)); 56 | } 57 | ); 58 | }} 59 | render={({ 60 | values, 61 | errors, 62 | touched, 63 | handleChange, 64 | handleBlur, 65 | handleSubmit, 66 | isSubmitting, 67 | }) => ( 68 |
69 | 76 | {touched.email && errors.email &&
{errors.email}
} 77 | 84 | {touched.password && errors.password &&
{errors.password}
} 85 | 88 |
89 | )} 90 | /> 91 |
92 | ); 93 | 94 | export default Basic; -------------------------------------------------------------------------------- /test/getTscErrors.test.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, mkdirSync } from "fs" 2 | import * as path from 'path' 3 | import { buildAndGetErrors } from '../src/utils/getTscErrors' 4 | import { downloadTsRepoAsync } from '../src/main' 5 | describe("getErrors", () => { 6 | jest.setTimeout(10 * 60 * 1000) 7 | 8 | beforeAll(async () => { 9 | if (!existsSync("./testDownloads/getErrors/typescript-test-fake-error/built/local/tsc.js")) { 10 | if (!existsSync("./testDownloads/getErrors")) { 11 | mkdirSync("./testDownloads/getErrors", { recursive: true }); 12 | } 13 | await downloadTsRepoAsync('./testDownloads/getErrors', 'https://github.com/sandersn/typescript', 'test-fake-error', 'tsc'); 14 | } 15 | }); 16 | 17 | it("builds a simple project one time", async () => { 18 | const errors = await buildAndGetErrors( 19 | "./testResources/simpleProject", 20 | /*monorepoPackages*/ [], 21 | /*isUserTestRepo*/ true, 22 | path.resolve("./testDownloads/getErrors/typescript-test-fake-error/built/local/tsc.js"), 23 | /*timeoutMs*/ 1e6, 24 | /*skipLibCheck*/ true, 25 | ) 26 | expect(errors.hasConfigFailure).toBeFalsy() 27 | expect(errors.projectErrors).toHaveLength(1) 28 | expect(errors.projectErrors[0].errors.length).toBe(1) 29 | expect(errors.projectErrors[0].errors[0]).toMatchObject({ 30 | code: 2496, 31 | text: "error TS2496: The 'arguments' object cannot be referenced in an arrow function in ES3 and ES5. Consider using a standard function expression.", 32 | }) 33 | expect(errors.projectErrors[0].errors[0].fileUrl?.endsWith("testResources/simpleProject/main.ts(1,35)")).toBeTruthy() 34 | expect(errors.projectErrors[0].errors[0].projectUrl?.endsWith("testResources/simpleProject/tsconfig.json")).toBeTruthy() 35 | }) 36 | it("builds a script project one time", async () => { 37 | const errors = await buildAndGetErrors( 38 | "./testResources/scriptProject", 39 | /*monorepoPackages*/ [], 40 | /*isUserTestRepo*/ true, 41 | path.resolve("./testDownloads/getErrors/typescript-test-fake-error/built/local/tsc.js"), 42 | /*timeoutMs*/ 1e6, 43 | /*skipLibCheck*/ true, 44 | ) 45 | expect(errors.hasConfigFailure).toBeFalsy() 46 | expect(errors.projectErrors).toHaveLength(1) 47 | expect(errors.projectErrors[0].errors.length).toBe(1) 48 | expect(errors.projectErrors[0].errors[0]).toMatchObject({ 49 | code: 2496, 50 | text: "error TS2496: The 'arguments' object cannot be referenced in an arrow function in ES3 and ES5. Consider using a standard function expression.", 51 | }) 52 | expect(errors.projectErrors[0].errors[0].fileUrl?.endsWith("testResources/scriptProject/main.ts(1,35)")).toBeTruthy() 53 | expect(errors.projectErrors[0].errors[0].projectUrl).toEqual("testResources/scriptProject/build.sh") 54 | }) 55 | xit("builds Real Live prettier, For Real", async () => { 56 | const errors = await buildAndGetErrors( 57 | "./testResources/scriptPrettier", 58 | /*monorepoPackages*/ [], 59 | /*isUserTestRepo*/ false, 60 | path.resolve("./testDownloads/getErrors/typescript-test-fake-error/built/local/tsc.js"), 61 | /*timeoutMs*/ 1e6, 62 | /*skipLibCheck*/ true, 63 | ) 64 | expect(errors.hasConfigFailure).toBeFalsy() 65 | expect(errors.projectErrors).toHaveLength(1) 66 | expect(errors.projectErrors[0].errors.length).toBe(37) 67 | expect(errors.projectErrors[0].errors[0]).toMatchObject({ 68 | code: 2496, 69 | text: "error TS2496: The 'arguments' object cannot be referenced in an arrow function in ES3 and ES5. Consider using a standard function expression.", 70 | }) 71 | expect(errors.projectErrors[0].errors[0].fileUrl?.endsWith("src/cli/logger.js(31,23)")).toBeTruthy() 72 | expect(errors.projectErrors[0].errors[0].projectUrl).toEqual("testResources/scriptPrettier/build.sh") 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch listTopRepos", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/dist/listTopRepos.js", 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "sourceMaps": true, 19 | "args": [ 20 | "TypeScript", 21 | "100", 22 | "0", 23 | "./artifacts/repos.json" 24 | ], 25 | "preLaunchTask": "Create artifacts folder" 26 | }, 27 | { 28 | "type": "node", 29 | "request": "launch", 30 | "name": "Launch listUserTestRepos", 31 | "skipFiles": [ 32 | "/**" 33 | ], 34 | "program": "${workspaceFolder}/dist/listUserTestRepos.js", 35 | "outFiles": [ 36 | "${workspaceFolder}/dist/**/*.js" 37 | ], 38 | "sourceMaps": true, 39 | "args": [ 40 | "./userTests", 41 | "./artifacts/repos.json" 42 | ], 43 | "preLaunchTask": "Create artifacts folder" 44 | }, 45 | { 46 | "type": "node", 47 | "request": "launch", 48 | "name": "Launch checkGithubRepos", 49 | "skipFiles": [ 50 | "/**" 51 | ], 52 | "program": "${workspaceFolder}/dist/checkGithubRepos.js", 53 | "sourceMaps": true, 54 | "outFiles": [ 55 | "${workspaceFolder}/dist/**/*.js" 56 | ], 57 | "args": [ 58 | "tsserver", 59 | "latest", 60 | "next", 61 | "./artifacts/repos.json", 62 | "1", 63 | "1", 64 | "./artifacts", 65 | "False", 66 | "testSeed" 67 | ], 68 | "preLaunchTask": "Clean ts_downloads and artifacts folders" 69 | }, 70 | { 71 | "type": "node", 72 | "request": "launch", 73 | "name": "Launch checkUserTestRepos", 74 | "skipFiles": [ 75 | "/**" 76 | ], 77 | "program": "${workspaceFolder}/dist/checkUserTestRepos.js", 78 | "sourceMaps": true, 79 | "outFiles": [ 80 | "${workspaceFolder}/dist/**/*.js" 81 | ], 82 | "args": [ 83 | "tsserver", 84 | "https://github.com/armanio123/TypeScript.git", 85 | "main", 86 | "2", 87 | "false", 88 | "./artifacts/repos.json", 89 | "1", 90 | "1", 91 | "./artifacts", 92 | "true", 93 | "testSeed" 94 | ], 95 | "preLaunchTask": "Clean ts_downloads and artifacts folders" 96 | }, 97 | { 98 | "type": "node", 99 | "request": "launch", 100 | "name": "Launch postGitHubComments", 101 | "skipFiles": [ 102 | "/**" 103 | ], 104 | "program": "${workspaceFolder}/dist/postGitHubComments.js", 105 | "sourceMaps": true, 106 | "outFiles": [ 107 | "${workspaceFolder}/dist/**/*.js" 108 | ], 109 | "args": [ 110 | "armanio123", 111 | "2", 112 | "1", 113 | "false", 114 | "artifacts", 115 | "artifacts", 116 | "false" 117 | ] 118 | } 119 | ] 120 | } -------------------------------------------------------------------------------- /src/postGithubIssue.ts: -------------------------------------------------------------------------------- 1 | import fs = require("fs"); 2 | import path = require("path"); 3 | import { artifactFolderUrlPlaceholder, getArtifactsApiUrlPlaceholder, Metadata, metadataFileName, RepoStatus, resultFileNameSuffix, StatusCounts, TsEntrypoint } from "./main"; 4 | import git = require("./utils/gitUtils"); 5 | import pu = require("./utils/packageUtils"); 6 | 7 | const { argv } = process; 8 | 9 | if (argv.length !== 11) { 10 | console.error(`Usage: ${path.basename(argv[0])} ${path.basename(argv[1])} `); 11 | process.exit(-1); 12 | } 13 | 14 | const [, , ep, language, repoCount, repoStartIndex, resultDirPath, logUri, artifactsUri, post, getArtifactsApi] = argv; 15 | const postResult = post.toLowerCase() === "true"; 16 | const entrypoint = ep as TsEntrypoint; 17 | 18 | const metadataFilePaths = pu.glob(resultDirPath, `**/${metadataFileName}`); 19 | 20 | let analyzedCount = 0; 21 | let totalCount = 0; 22 | const statusCounts: StatusCounts = {}; 23 | 24 | let newTscResolvedVersion: string | undefined; 25 | let oldTscResolvedVersion: string | undefined; 26 | 27 | for (const path of metadataFilePaths) { 28 | const metadata: Metadata = JSON.parse(fs.readFileSync(path, { encoding: "utf-8" })); 29 | 30 | newTscResolvedVersion ??= metadata.newTsResolvedVersion; 31 | oldTscResolvedVersion ??= metadata.oldTsResolvedVersion; 32 | 33 | for (const s in metadata.statusCounts) { 34 | const status = s as RepoStatus; 35 | const count = metadata.statusCounts[status]!; 36 | statusCounts[status] = (statusCounts[status] ?? 0) + count; 37 | totalCount += count; 38 | switch (status) { 39 | case "Detected no interesting changes": 40 | case "Detected interesting changes": 41 | analyzedCount += count; 42 | break; 43 | } 44 | } 45 | } 46 | 47 | 48 | const title = `${entrypoint === "tsserver" ? `[ServerErrors][${language}]` : `[NewErrors]`} ${newTscResolvedVersion} vs ${oldTscResolvedVersion}`; 49 | 50 | const description = entrypoint === "tsserver" 51 | ? `The following errors were reported by ${newTscResolvedVersion} vs ${oldTscResolvedVersion}` 52 | : `The following errors were reported by ${newTscResolvedVersion}, but not by ${oldTscResolvedVersion}`; 53 | let header = `${description} 54 | [Pipeline that generated this bug](https://typescript.visualstudio.com/TypeScript/_build?definitionId=48) 55 | [Logs for the pipeline run](${logUri}) 56 | [File that generated the pipeline](https://github.com/microsoft/typescript-error-deltas/blob/main/azure-pipelines-gitTests.yml) 57 | 58 | This run considered ${repoCount} popular TS repos from GH (after skipping the top ${repoStartIndex}). 59 | 60 |
61 | Successfully analyzed ${analyzedCount} of ${totalCount} visited repos${totalCount < +repoCount ? ` (:warning: expected ${repoCount})` : ""} 62 | 63 | | Outcome | Count | 64 | |---------|-------| 65 | ${Object.keys(statusCounts).sort().map(status => `| ${status} | ${statusCounts[status as RepoStatus]} |\n`).join("")} 66 |
67 | 68 | ## Investigation Status 69 | | Repo | Errors | Outcome | 70 | |------|--------|---------| 71 | `; 72 | 73 | const resultPaths = pu.glob(resultDirPath, `**/*.${resultFileNameSuffix}`).sort((a, b) => path.basename(a).localeCompare(path.basename(b))); 74 | const outputs = resultPaths.map(p => 75 | fs.readFileSync(p, { encoding: "utf-8" }) 76 | .replaceAll(artifactFolderUrlPlaceholder, artifactsUri) 77 | .replaceAll(getArtifactsApiUrlPlaceholder, getArtifactsApi)); 78 | 79 | 80 | // tsserver groups results by error, causing the summary to not make sense. Remove the list for now. 81 | // See issue: https://github.com/microsoft/typescript-error-deltas/issues/114 82 | if (entrypoint !== "tsserver") { 83 | // Prints the investigation status list. 84 | for (let i = 0; i < outputs.length; i++) { 85 | const resultPath = resultPaths[i]; 86 | const output = outputs[i]; 87 | 88 | const fileName = path.basename(resultPath); 89 | const repoString = fileName.substring(0, fileName.length - resultFileNameSuffix.length - 1); 90 | const repoName = repoString.replace(".", "/"); // The owner *probably* doesn't have a dot in it 91 | 92 | let errorCount = 0; 93 | 94 | const re = /^\W*error TS\d+/gm; 95 | while (re.exec(output)) { 96 | errorCount++; 97 | } 98 | 99 | header += `|${repoName}|${errorCount}| |\n`; 100 | } 101 | } 102 | 103 | 104 | const bodyChunks = [header, ...outputs]; 105 | git.createIssue(postResult, title, bodyChunks, /*sawNewErrors*/ !!outputs.length); -------------------------------------------------------------------------------- /src/utils/execUtils.ts: -------------------------------------------------------------------------------- 1 | import cp = require("child_process"); 2 | import path = require("path"); 3 | 4 | export async function execAsync(cwd: string, command: string): Promise { 5 | return new Promise((resolve, reject) => { 6 | console.log(`${cwd}> ${command}`); 7 | cp.exec(command, { cwd }, (err, stdout, stderr) => { 8 | if (stdout?.length) { 9 | console.log(stdout); 10 | } 11 | if (stderr?.length) { 12 | console.log(stderr); // To stdout to maintain order 13 | } 14 | 15 | if (err) { 16 | return reject(err); 17 | } 18 | return resolve(stdout); 19 | }); 20 | }); 21 | } 22 | 23 | export interface SpawnResult { 24 | stdout: string, 25 | stderr: string, 26 | code: number | null, 27 | signal: NodeJS.Signals | null, 28 | } 29 | 30 | /** Returns undefined if and only if executions times out. */ 31 | export function spawnWithTimeoutAsync(cwd: string, command: string, args: readonly string[], timeoutMs: number, env?: {}): Promise { 32 | console.log(`${cwd}> ${command} ${args.join(" ")}`); 33 | return new Promise((resolve, reject) => { 34 | if (timeoutMs <= 0) { 35 | resolve(undefined); 36 | return; 37 | } 38 | 39 | // We use `spawn`, rather than `execFile`, because package installation tends to write a lot 40 | // of data to stdout, overflowing `execFile`'s buffer. 41 | const childProcess = cp.spawn(command, args, { 42 | cwd, 43 | env, 44 | windowsHide: true, 45 | }); 46 | 47 | let timedOut = false; 48 | 49 | let stdout = ""; 50 | let stderr = ""; 51 | 52 | childProcess.once("close", (code, signal) => { 53 | if (!timedOut) { 54 | clearTimeout(timeout); 55 | resolve({ stdout, stderr, code, signal }); 56 | } 57 | }); 58 | 59 | childProcess.stdout.on("data", data => { 60 | stdout += data; 61 | }); 62 | 63 | childProcess.stderr.on("data", data => { 64 | stderr += data; 65 | }); 66 | 67 | const timeout = setTimeout(async () => { 68 | timedOut = true; 69 | await killTree(childProcess); 70 | resolve(undefined); 71 | }, timeoutMs | 0); // Truncate to int 72 | }); 73 | } 74 | 75 | function killTree(childProcess: cp.ChildProcessWithoutNullStreams): Promise { 76 | return new Promise((resolve, reject) => { 77 | // Ideally, we would wait for all of the processes to close, but we only get events for 78 | // this one, so we'll kill it last and hope for the best. 79 | childProcess.once("close", () => { 80 | resolve(); 81 | }); 82 | 83 | cp.exec("ps -e -o pid,ppid --no-headers", (err, stdout) => { 84 | if (err) { 85 | reject (err); 86 | return; 87 | } 88 | 89 | const childProcessPid = childProcess.pid!; 90 | let sawChildProcessPid = false; 91 | 92 | const childMap: Record = {}; 93 | const pidList = stdout.trim().split(/\s+/); 94 | for (let i = 0; i + 1 < pidList.length; i += 2) { 95 | const childPid = +pidList[i]; 96 | const parentPid = +pidList[i + 1]; 97 | 98 | childMap[parentPid] ||= []; 99 | childMap[parentPid].push(childPid); 100 | 101 | sawChildProcessPid ||= childPid === childProcessPid; 102 | } 103 | 104 | if (!sawChildProcessPid) { 105 | // Descendent processes may still be alive, but we have no way to identify them 106 | resolve(); 107 | return; 108 | } 109 | 110 | const strictDescendentPids: number[] = []; 111 | const stack: number[] = [ childProcessPid ]; 112 | while (stack.length) { 113 | const pid = stack.pop()!; 114 | if (pid !== childProcessPid) { 115 | strictDescendentPids.push(pid); 116 | } 117 | const children = childMap[pid]; 118 | if (children) { 119 | stack.push(...children); 120 | } 121 | } 122 | 123 | console.log(`Killing process ${childProcessPid} and its descendents: ${strictDescendentPids.join(", ")}`); 124 | 125 | strictDescendentPids.forEach(pid => process.kill(pid)); 126 | childProcess.kill(); 127 | // Resolve when we detect that childProcess has closed (above) 128 | }); 129 | }); 130 | } 131 | -------------------------------------------------------------------------------- /azure-pipelines-gitTests-template.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: POST_RESULT 3 | displayName: Post GitHub issue with results 4 | type: boolean 5 | default: true 6 | - name: DIAGNOSTIC_OUTPUT 7 | displayName: Log diagnostic data 8 | type: boolean 9 | default: false 10 | - name: REPO_COUNT 11 | displayName: Repo Count 12 | type: number 13 | default: 300 14 | - name: REPO_START_INDEX 15 | displayName: Repo Start Index 16 | type: number 17 | default: 0 18 | - name: OLD_VERSION 19 | displayName: Baseline TypeScript package version 20 | type: string 21 | default: latest 22 | - name: NEW_VERSION 23 | displayName: Candidate TypeScript package version 24 | type: string 25 | default: next 26 | - name: MACHINE_COUNT 27 | displayName: Machine Count 28 | type: number 29 | default: 8 30 | - name: ENTRYPOINT 31 | displayName: TypeScript entrypoint 32 | type: string 33 | - name: LANGUAGE 34 | displayName: Language of repos on GitHub (tsserver only) 35 | type: string 36 | - name: PRNG_SEED 37 | displayName: Pseudo-random number generator seed 38 | type: string 39 | default: 'n/a' 40 | 41 | jobs: 42 | - job: ListRepos 43 | pool: 44 | name: TypeScript-1ES-Deploys 45 | demands: 46 | - ImageOverride -equals azure-linux-3 47 | steps: 48 | - task: AzureKeyVault@2 49 | inputs: 50 | azureSubscription: 'TypeScript Public CI' 51 | KeyVaultName: 'jststeam-passwords' 52 | SecretsFilter: 'typescript-bot-github-PAT-error-deltas' 53 | displayName: Get secrets 54 | retryCountOnTaskFailure: 3 55 | - task: UseNode@1 56 | inputs: 57 | version: '20.x' 58 | displayName: 'Install Node.js' 59 | - script: | 60 | npm ci 61 | npm run build 62 | mkdir artifacts 63 | node dist/listTopRepos ${{ parameters.LANGUAGE }} ${{ parameters.REPO_COUNT }} ${{ parameters.REPO_START_INDEX }} artifacts/repos.json 64 | displayName: 'List top TS repos' 65 | env: 66 | GITHUB_PAT: $(typescript-bot-github-PAT-error-deltas) 67 | - publish: artifacts 68 | artifact: RepoList 69 | - job: DetectNewErrors 70 | dependsOn: ListRepos 71 | continueOnError: true 72 | timeoutInMinutes: 360 73 | strategy: 74 | parallel: ${{ parameters.MACHINE_COUNT }} 75 | steps: 76 | - download: current 77 | artifact: RepoList 78 | - task: UseNode@1 79 | inputs: 80 | version: '20.x' 81 | displayName: 'Install Node.js' 82 | - script: | 83 | df -h 84 | df -h -i 85 | displayName: Debugging 86 | continueOnError: true 87 | - script: | 88 | npm ci 89 | npm run build 90 | npm install -g yarn 91 | npm install -g pnpm 92 | npm install -g corepack@latest 93 | export COREPACK_ENABLE_AUTO_PIN=0 94 | export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 95 | export COREPACK_ENABLE_STRICT=0 96 | corepack enable 97 | corepack enable npm 98 | mkdir 'RepoResults$(System.JobPositionInPhase)' 99 | node dist/checkGithubRepos ${{ parameters.ENTRYPOINT }} ${{ parameters.OLD_VERSION }} ${{ parameters.NEW_VERSION }} '$(Pipeline.Workspace)/RepoList/repos.json' $(System.TotalJobsInPhase) $(System.JobPositionInPhase) 'RepoResults$(System.JobPositionInPhase)' ${{ parameters.DIAGNOSTIC_OUTPUT }} ${{ parameters.PRNG_SEED }} 100 | displayName: 'Run TypeScript on repos' 101 | continueOnError: true 102 | - publish: 'RepoResults$(System.JobPositionInPhase)' 103 | artifact: 'RepoResults$(System.JobPositionInPhase)' 104 | - job: ReportNewErrors 105 | dependsOn: DetectNewErrors 106 | pool: 107 | name: TypeScript-1ES-Deploys 108 | demands: 109 | - ImageOverride -equals azure-linux-3 110 | steps: 111 | - task: AzureKeyVault@2 112 | inputs: 113 | azureSubscription: 'TypeScript Public CI' 114 | KeyVaultName: 'jststeam-passwords' 115 | SecretsFilter: 'typescript-bot-github-PAT-error-deltas' 116 | displayName: Get secrets 117 | retryCountOnTaskFailure: 3 118 | - download: current 119 | - task: UseNode@1 120 | inputs: 121 | version: '20.x' 122 | displayName: 'Install Node.js' 123 | - script: | 124 | npm ci 125 | npm run build 126 | node dist/postGithubIssue ${{ parameters.ENTRYPOINT }} ${{ parameters.LANGUAGE }} ${{ parameters.REPO_COUNT }} ${{ parameters.REPO_START_INDEX }} '$(Pipeline.Workspace)' '$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)' '$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)&view=artifacts&type=publishedArtifacts' ${{ parameters.POST_RESULT }} '$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/artifacts' 127 | displayName: 'Create issue from new errors' 128 | env: 129 | GITHUB_PAT: $(typescript-bot-github-PAT-error-deltas) 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typescript-error-deltas 2 | 3 | Download and compile popular open source repos in order to compare new versions of the TypeScript compiler with the current version. 4 | For example, this project will clone the prettier repo and compile it with the current version of TypeScript. 5 | Then it will compile it with a version of TypeScript from a pull request. 6 | Afterward it will compile new errors that are issued only with the new version and post them as a comment on the pull request. 7 | 8 | There is no comparison of types, errors, symbols or language service output. 9 | 10 | ## Running 11 | 12 | To run online, you can 13 | 14 | * [Run the new error detector from Azure Pipelines](https://typescript.visualstudio.com/TypeScript/_build?definitionId=48) to create a new issue on the TypeScript repository. 15 | * [Tag typescript-bot](https://github.com/microsoft/TypeScript/wiki/Triggering-TypeScript-Bot) and write a comment of the form `@typescript-bot user test this` on a pull request to get an inline report of new errors. 16 | 17 | These commands can also be run locally. 18 | 19 | ```sh 20 | # New Error Detector (a.k.a. "git tests") 21 | node dist/checkGithubRepos.js [post-results] [repo-count] [repo-start-index] [old-ts-version-on-npm] [old-ts-version-on-npm] 22 | 23 | # Inline User Test Reporter (a.k.a. "user tests") 24 | node dist/checkUserTestRepos.js [post-results] [ts-repo-url] [head-ref] [requesting-user] [source-issue] [github-comment-id-for-updates] [query-repos-by-stars] 25 | 26 | ``` 27 | 28 | You can view example usage of these commands from how they're currently triggered on Azure Pipelines: 29 | 30 | * [New Error Detector](https://github.com/microsoft/typescript-error-deltas/blob/main/azure-pipelines-gitTests.yml) 31 | * [Inline User Test Reporter](https://github.com/microsoft/typescript-error-deltas/blob/main/azure-pipelines-userTests.yml) 32 | 33 | ## Contributing 34 | 35 | ### User Tests 36 | 37 | There are three kinds of user tests, all of which aim to use popular packages with different versions of TypeScript: 38 | 39 | 1. Example projects, which specify a popular package in package.json and then provide an example use of it. 40 | 2. Clones of a repo of a popular package, built with `tsc`. 41 | 3. Clones of a repo of a popular package, built with a custom `bash` script. 42 | 43 | #### Example projects 44 | 45 | Use `userTests/axios` as an example: 46 | 47 | - Create a package.json with `axios` as a dependency. 48 | - Create an example program that uses `axios`. In our case, just: 49 | - index.ts 50 | - tsconfig.json 51 | 52 | The example projects could be as large as a complete app as long as it compiles with a single invocation of `tsc`. 53 | However, the current projects almost all consist of a single import, like `import x = require('x')`. 54 | This could obviously be improved. 55 | 56 | #### Clone repos (simple build) 57 | 58 | Use `userTests/axios-src` as an example: 59 | 60 | Create `test.json` like the following: 61 | 62 | ``` json 63 | { 64 | "cloneUrl": "https://github.com/axios/axios.git", 65 | "types": ["node"] 66 | } 67 | ``` 68 | 69 | The `types` field is optional; it installs `@types/` packages for each entry in its array before running `tsc`. 70 | This is mostly useful if the package isn't written in TypeScript and doesn't include types in its own devDependencies. 71 | 72 | Like the example projects, the cloned repos must be buildable with a single invocation of `tsc`. 73 | 74 | #### Clone repos (script build) 75 | 76 | Use `userTests/azure-sdk` as an example; create a script `build.sh` that: 77 | 78 | - Clones a repo. 79 | - Installs its dependencies. 80 | - Alters its TypeScript dependency to use a custom TypeScript version. 81 | - Builds the repo. 82 | 83 | The details vary considerably from project to project. 84 | This kind of test allows you to build arbitrary projects. 85 | 86 | ### Legal 87 | 88 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 89 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 90 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 91 | 92 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 93 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 94 | provided by the bot. You will only need to do this once across all repos using our CLA. 95 | 96 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 97 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 98 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 99 | 100 | ## Trademarks 101 | 102 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 103 | trademarks or logos is subject to and must follow 104 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 105 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 106 | Any use of third-party trademarks or logos are subject to those third-party's policies. 107 | -------------------------------------------------------------------------------- /azure-pipelines-userTests.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: POST_RESULT 3 | displayName: Post GitHub comment with results 4 | type: boolean 5 | default: true 6 | - name: DIAGNOSTIC_OUTPUT 7 | displayName: Log diagnostic data 8 | type: boolean 9 | default: false 10 | - name: TOP_REPOS 11 | displayName: Whether to query Github for top TS repos by stars. If false, uses the user test suite. 12 | type: boolean 13 | default: false 14 | - name: REPO_COUNT 15 | displayName: Number of repositories to run on for when TOP_REPOS is set to true. 16 | type: number 17 | default: 100 18 | - name: OLD_TS_REPO_URL 19 | displayName: Old Typscript Repo Url 20 | type: string 21 | default: https://github.com/microsoft/TypeScript.git 22 | - name: OLD_HEAD_REF 23 | displayName: Old head reference 24 | type: string 25 | default: main 26 | - name: DISTINCT_ID 27 | displayName: Distinct ID 28 | type: string 29 | - name: REQUESTING_USER 30 | displayName: User to tag when the results are ready 31 | type: string 32 | - name: SOURCE_ISSUE 33 | displayName: PR ID in github 34 | type: number 35 | - name: STATUS_COMMENT 36 | displayName: Typescript-bot comment ID indicating that the run started 37 | type: number 38 | - name: MACHINE_COUNT 39 | displayName: Machine Count 40 | type: number 41 | default: 16 42 | values: 43 | - 1 44 | - 2 45 | - 4 46 | - 8 47 | - 16 48 | - name: ENTRYPOINT 49 | displayName: TypeScript entrypoint 50 | type: string 51 | default: tsc 52 | values: 53 | - tsc 54 | - tsserver 55 | - name: PRNG_SEED 56 | displayName: Pseudo-random number generator seed 57 | type: string 58 | default: 'n/a' 59 | 60 | pr: none 61 | trigger: none 62 | 63 | pool: 64 | name: TypeScript-1ES-Large 65 | demands: 66 | - ImageOverride -equals azure-linux-3 67 | 68 | variables: 69 | Codeql.Enabled: false 70 | skipComponentGovernanceDetection: true 71 | 72 | jobs: 73 | - job: ListRepos 74 | pool: 75 | name: TypeScript-1ES-Deploys 76 | demands: 77 | - ImageOverride -equals azure-linux-3 78 | steps: 79 | - task: AzureKeyVault@2 80 | inputs: 81 | azureSubscription: 'TypeScript Public CI' 82 | KeyVaultName: 'jststeam-passwords' 83 | SecretsFilter: 'typescript-bot-github-PAT-error-deltas' 84 | displayName: Get secrets 85 | retryCountOnTaskFailure: 3 86 | - task: UseNode@1 87 | inputs: 88 | version: '20.x' 89 | displayName: 'Install Node.js' 90 | - script: | 91 | npm ci 92 | npm run build 93 | mkdir artifacts 94 | - script: node dist/listTopRepos TypeScript ${{ parameters.REPO_COUNT }} 0 artifacts/repos.json 95 | condition: eq('${{ parameters.TOP_REPOS }}', 'true') 96 | env: 97 | GITHUB_PAT: $(typescript-bot-github-PAT-error-deltas) 98 | displayName: 'List top TS repos' 99 | - script: node dist/listUserTestRepos ./userTests artifacts/repos.json 100 | condition: ne('${{ parameters.TOP_REPOS }}', 'true') 101 | displayName: 'List user test repos' 102 | - publish: artifacts 103 | artifact: RepoList 104 | - job: DetectNewErrors 105 | dependsOn: ListRepos 106 | timeoutInMinutes: 360 107 | strategy: 108 | parallel: ${{ parameters.MACHINE_COUNT }} 109 | steps: 110 | - download: current 111 | artifact: RepoList 112 | - task: UseNode@1 113 | inputs: 114 | version: '20.x' 115 | displayName: 'Install Node.js' 116 | - script: | 117 | df -h 118 | df -h -i 119 | displayName: Debugging 120 | continueOnError: true 121 | - script: | 122 | npm ci 123 | npm run build 124 | npm install -g yarn 125 | npm install -g pnpm 126 | npm install -g corepack@latest 127 | export COREPACK_ENABLE_AUTO_PIN=0 128 | export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 129 | export COREPACK_ENABLE_STRICT=0 130 | corepack enable 131 | corepack enable npm 132 | mkdir 'RepoResults$(System.JobPositionInPhase)' 133 | node dist/checkUserTestRepos ${{ parameters.ENTRYPOINT }} ${{ parameters.OLD_TS_REPO_URL }} ${{ parameters.OLD_HEAD_REF }} ${{ parameters.SOURCE_ISSUE }} ${{ parameters.TOP_REPOS }} '$(Pipeline.Workspace)/RepoList/repos.json' $(System.TotalJobsInPhase) $(System.JobPositionInPhase) 'RepoResults$(System.JobPositionInPhase)' ${{ parameters.DIAGNOSTIC_OUTPUT }} ${{ parameters.PRNG_SEED }} 134 | displayName: 'Run user tests' 135 | - publish: 'RepoResults$(System.JobPositionInPhase)' 136 | artifact: 'RepoResults$(System.JobPositionInPhase)' 137 | - job: ReportNewErrors 138 | dependsOn: DetectNewErrors 139 | pool: 140 | name: TypeScript-1ES-Deploys 141 | demands: 142 | - ImageOverride -equals azure-linux-3 143 | steps: 144 | - task: AzureKeyVault@2 145 | inputs: 146 | azureSubscription: 'TypeScript Public CI' 147 | KeyVaultName: 'jststeam-passwords' 148 | SecretsFilter: 'typescript-bot-github-PAT-error-deltas' 149 | displayName: Get secrets 150 | retryCountOnTaskFailure: 3 151 | - download: current 152 | - task: UseNode@1 153 | inputs: 154 | version: '20.x' 155 | displayName: 'Install Node.js' 156 | - script: | 157 | npm ci 158 | npm run build 159 | node dist/postGithubComments ${{ parameters.ENTRYPOINT }} ${{ parameters.REQUESTING_USER }} ${{ parameters.SOURCE_ISSUE }} ${{ parameters.STATUS_COMMENT }} ${{ parameters.DISTINCT_ID }} ${{ parameters.TOP_REPOS }} '$(Pipeline.Workspace)' '$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)&view=artifacts&type=publishedArtifacts' ${{ parameters.POST_RESULT }} ${{ parameters.REPO_COUNT }} '$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/artifacts' 160 | displayName: 'Update PR comment with new errors' 161 | env: 162 | GITHUB_PAT: $(typescript-bot-github-PAT-error-deltas) 163 | -------------------------------------------------------------------------------- /src/utils/packageUtils.ts: -------------------------------------------------------------------------------- 1 | import fs = require("fs"); 2 | import globCps = require("glob"); 3 | import json5 = require("json5"); 4 | import path = require("path"); 5 | import yaml = require("js-yaml"); 6 | 7 | interface Package { 8 | meta_dir: string, 9 | meta_state: "unvisited" | "visiting" | "visited", 10 | name: string, 11 | workspaces?: readonly string[] | { packages: readonly string[] }, 12 | dependencies?: readonly string[], 13 | devDependencies?: readonly string[], 14 | peerDependencies?: readonly string[], 15 | } 16 | 17 | /** 18 | * `glob`, but ignoring node_modules and symlinks, and returning absolute paths. 19 | */ 20 | export function glob(cwd: string, pattern: string): string[] { 21 | return globCps.sync(pattern, { cwd, absolute: true, ignore: "**/node_modules/**", follow: false }) 22 | } 23 | 24 | /** 25 | * Returns true if the path exists. 26 | */ 27 | export async function exists(path: string): Promise { 28 | return new Promise(resolve => fs.exists(path, e => resolve(e))); 29 | } 30 | 31 | /** 32 | * Heuristically returns a list of package.json paths in monorepo dependency order. 33 | * NB: Does not actually consume lerna.json. 34 | */ 35 | export async function getMonorepoOrder(repoDir: string): Promise { 36 | const yarnOrNpmLockFiles = glob(repoDir, "**/{yarn.lock,package-lock.json}"); 37 | if (yarnOrNpmLockFiles.length) { 38 | const workspaceOrder: string[] = []; 39 | for (const lockFile of yarnOrNpmLockFiles) { 40 | const dir = path.dirname(lockFile); 41 | const pkgPath = path.join(dir, "package.json"); 42 | if (await exists(pkgPath)) { 43 | const contents = await fs.promises.readFile(pkgPath, { encoding: "utf-8" }); 44 | const pkg: Package = json5.parse(contents); 45 | const workspaces = pkg.workspaces; 46 | if (workspaces) { 47 | const workspaceDirs = "packages" in workspaces ? workspaces.packages : workspaces; 48 | for (const workspaceDir of workspaceDirs) { 49 | // workspaceDir might end with `/*` - glob will do the right thing 50 | const pkgPaths = glob(dir, path.join(workspaceDir, "package.json")); 51 | await appendOrderedMonorepoPackages(pkgPaths, workspaceOrder); 52 | } 53 | } 54 | } 55 | } 56 | if (workspaceOrder.length) { 57 | return workspaceOrder; 58 | } 59 | } 60 | 61 | const pnpmWorkspaceFiles = glob(repoDir, "**/pnpm-workspace.yaml"); 62 | if (pnpmWorkspaceFiles.length) { 63 | const pnpmWorkspaceOrder: string[] = []; 64 | for (const pnpmWorkspaceFile of pnpmWorkspaceFiles) { 65 | const contents = await fs.promises.readFile(pnpmWorkspaceFile, { encoding: "utf-8" }); 66 | const config = yaml.load(contents) as { packages?: string[] } | undefined; // undefined for an empty test fixture 67 | const workspaceDirs = config?.packages; 68 | if (workspaceDirs) { 69 | const pnpmDir = path.dirname(pnpmWorkspaceFile); 70 | for (const workspaceDir of workspaceDirs) { 71 | // CONSIDER: Should technically exclude those beginning with `!` 72 | if (workspaceDir.startsWith("!")) continue; 73 | // workspaceDir might end with `/*` - glob will do the right thing 74 | const pkgPaths = glob(pnpmDir, path.join(workspaceDir, "package.json")); 75 | await appendOrderedMonorepoPackages(pkgPaths, pnpmWorkspaceOrder); 76 | } 77 | } 78 | } 79 | if (pnpmWorkspaceOrder.length) { 80 | return pnpmWorkspaceOrder; 81 | } 82 | } 83 | 84 | const lernaFiles = glob(repoDir, "**/lerna.json"); 85 | if (lernaFiles.length) { 86 | const lernaOrder: string[] = []; 87 | for (const lernaFile of lernaFiles) { 88 | const lernaDir = path.dirname(lernaFile); 89 | if (await exists(path.join(lernaDir, "packages"))) { 90 | const pkgPaths = glob(path.join(lernaDir, "packages"), "**/package.json"); 91 | await appendOrderedMonorepoPackages(pkgPaths, lernaOrder); 92 | } 93 | } 94 | if (lernaOrder.length) { 95 | return lernaOrder; 96 | } 97 | } 98 | 99 | return []; 100 | } 101 | 102 | async function appendOrderedMonorepoPackages(pkgPaths: string[], monorepoOrder: string[]) { 103 | const pkgs = await Promise.all(pkgPaths.map(async (pkgPath) => { 104 | const contents = await fs.promises.readFile(pkgPath, { encoding: "utf-8" }); 105 | const pkg: Package = json5.parse(contents); 106 | pkg.meta_dir = path.dirname(pkgPath); 107 | pkg.meta_state = "unvisited"; 108 | return pkg; 109 | })); 110 | const pkgMap: Record = {}; 111 | for (const pkg of pkgs) { 112 | pkgMap[pkg.name] = pkg; 113 | } 114 | 115 | while (true) { 116 | const pkg = pkgs.find(p => p.meta_state === "unvisited"); 117 | if (!pkg) break; 118 | visit(pkg); 119 | } 120 | 121 | function visit(pkg: Package): void { 122 | // "visiting" indicates a cycle, which some monorepo systems (e.g. lerna) allow 123 | if (pkg.meta_state !== "unvisited") return; 124 | 125 | pkg.meta_state = "visiting"; 126 | 127 | if (pkg.dependencies) { 128 | for (const dep in pkg.dependencies) { 129 | const depPkg = pkgMap[dep]; 130 | if (depPkg) visit(depPkg); 131 | } 132 | } 133 | 134 | if (pkg.devDependencies) { 135 | for (const dep in pkg.devDependencies) { 136 | const depPkg = pkgMap[dep]; 137 | if (depPkg) visit(depPkg); 138 | } 139 | } 140 | 141 | if (pkg.peerDependencies) { 142 | for (const dep in pkg.peerDependencies) { 143 | const depPkg = pkgMap[dep]; 144 | if (depPkg) visit(depPkg); 145 | } 146 | } 147 | 148 | pkg.meta_state = "visited"; 149 | monorepoOrder.push(pkg.meta_dir); 150 | } 151 | } 152 | 153 | -------------------------------------------------------------------------------- /src/postGithubComments.ts: -------------------------------------------------------------------------------- 1 | import fs = require("fs"); 2 | import path = require("path"); 3 | import { artifactFolderUrlPlaceholder, getArtifactsApiUrlPlaceholder, Metadata, metadataFileName, RepoStatus, resultFileNameSuffix } from "./main"; 4 | import git = require("./utils/gitUtils"); 5 | import pu = require("./utils/packageUtils"); 6 | import { asMarkdownInlineCode } from "./utils/markdownUtils"; 7 | 8 | const { argv } = process; 9 | 10 | if (argv.length !== 13) { 11 | console.error(`Usage: ${path.basename(argv[0])} ${path.basename(argv[1])} `); 12 | process.exit(-1); 13 | } 14 | 15 | const [, , entrypoint, userToTag, prNumber, commentNumber, distinctId, isTop, resultDirPath, artifactsUri, post, repoCount, getArtifactsApi] = argv; 16 | const isTopReposRun = isTop.toLowerCase() === "true"; 17 | const postResult = post.toLowerCase() === "true"; 18 | 19 | const metadataFilePaths = pu.glob(resultDirPath, `**/${metadataFileName}`); 20 | 21 | let newTscResolvedVersion: string | undefined; 22 | let oldTscResolvedVersion: string | undefined; 23 | 24 | let somethingChanged = false; 25 | const infrastructureFailures = new Map(); 26 | 27 | for (const path of metadataFilePaths) { 28 | const metadata: Metadata = JSON.parse(fs.readFileSync(path, { encoding: "utf-8" })); 29 | 30 | newTscResolvedVersion ??= metadata.newTsResolvedVersion; 31 | oldTscResolvedVersion ??= metadata.oldTsResolvedVersion; 32 | 33 | for (const s in metadata.statusCounts) { 34 | const status = s as RepoStatus; 35 | switch (status) { 36 | case "Detected no interesting changes": 37 | break; 38 | case "Detected interesting changes": 39 | somethingChanged = true; 40 | break; 41 | default: 42 | infrastructureFailures.set(status, (infrastructureFailures.get(status) ?? 0) + 1) 43 | break; 44 | } 45 | } 46 | } 47 | 48 | const summary: string[] = []; 49 | 50 | // In a top-repos run, the test set is arbitrary, so we ignore infrastructure failures 51 | // as it's possible that there's a repo that just doesn't work. 52 | if (!isTopReposRun && infrastructureFailures.size) { 53 | summary.push("There were infrastructure failures potentially unrelated to your change:"); 54 | summary.push(""); 55 | for (const [status, count] of infrastructureFailures) { 56 | summary.push(`- ${count} ${count === 1 ? "instance" : "instances"} of "${status}"`); 57 | } 58 | summary.push(""); 59 | summary.push("Otherwise..."); 60 | summary.push(""); 61 | } 62 | 63 | if (somethingChanged) { 64 | summary.push("Something interesting changed - please have a look."); 65 | } 66 | else { 67 | summary.push("Everything looks good!"); 68 | } 69 | 70 | // Files starting with an exclamation point are old server errors. 71 | const hasOldErrors = pu.glob(resultDirPath, `**/!*.${resultFileNameSuffix}`).length !== 0; 72 | 73 | const resultPaths = pu.glob(resultDirPath, `**/*.${resultFileNameSuffix}`).sort((a, b) => path.basename(a).localeCompare(path.basename(b))); 74 | const outputs = resultPaths.map(p => 75 | fs.readFileSync(p, { encoding: "utf-8" }) 76 | .replaceAll(artifactFolderUrlPlaceholder, artifactsUri) 77 | .replaceAll(getArtifactsApiUrlPlaceholder, getArtifactsApi)); 78 | 79 | const suiteDescription = isTopReposRun ? `top ${repoCount} repos` : "user tests"; 80 | let header = `@${userToTag} Here are the results of running the ${suiteDescription} with ${entrypoint} comparing ${asMarkdownInlineCode(oldTscResolvedVersion ?? "old")} and ${asMarkdownInlineCode(newTscResolvedVersion ?? "new")}: 81 | 82 | ${summary.join("\n")}`; 83 | 84 | if (!outputs.length) { 85 | git.createComment(+prNumber, +commentNumber, distinctId, postResult, [header], somethingChanged); 86 | } 87 | else { 88 | const oldErrorHeader = `

:warning: Old server errors :warning:

`; 89 | const openDetails = `\n\n
\nDetails\n\n`; 90 | const closeDetails = `\n
`; 91 | const initialHeader = header + openDetails + (hasOldErrors ? oldErrorHeader : ''); 92 | const continuationHeader = `@${userToTag} Here are some more interesting changes from running the ${suiteDescription} suite${openDetails}`; 93 | const trunctationSuffix = `\n:error: Truncated - see log for full output :error:`; 94 | 95 | // GH caps the maximum body length, so paginate if necessary 96 | const maxCommentLength = 65535; 97 | 98 | const bodyChunks: string[] = []; 99 | let chunk = initialHeader; 100 | 101 | for (let i = 0; i < outputs.length;) { 102 | const output = outputs[i]; 103 | if ((chunk.length + output.length + closeDetails.length) < maxCommentLength) { 104 | // Output still fits within chunk; add and continue. 105 | chunk += output; 106 | i++; 107 | continue; 108 | } 109 | 110 | // The output is too long to fit in the current chunk. 111 | 112 | if (chunk === initialHeader || chunk === continuationHeader) { 113 | // We only have a header, but the output still doesn't fit. Truncate and continue. 114 | console.log("Truncating output to fit in GH comment"); 115 | chunk += output.slice(0, maxCommentLength - chunk.length - closeDetails.length - trunctationSuffix.length); 116 | chunk += trunctationSuffix; 117 | chunk += closeDetails; 118 | bodyChunks.push(chunk); 119 | chunk = continuationHeader; 120 | i++; 121 | continue; 122 | } 123 | 124 | // Close the chunk and try the same output again. 125 | chunk += closeDetails; 126 | bodyChunks.push(chunk); 127 | chunk = continuationHeader; 128 | } 129 | 130 | if (chunk !== initialHeader && chunk !== continuationHeader) { 131 | chunk += closeDetails; 132 | bodyChunks.push(chunk); 133 | } 134 | 135 | 136 | for (const chunk of bodyChunks) { 137 | console.log(`Chunk of size ${chunk.length}`); 138 | } 139 | 140 | git.createComment(+prNumber, +commentNumber, distinctId, postResult, bodyChunks, somethingChanged); 141 | } 142 | -------------------------------------------------------------------------------- /src/utils/installPackages.ts: -------------------------------------------------------------------------------- 1 | import fs = require("fs"); 2 | import json5 = require("json5"); 3 | import path = require("path"); 4 | import utils = require("./packageUtils"); 5 | 6 | /** 7 | * String value will be the unqualified command name. 8 | */ 9 | export enum InstallTool { 10 | Npm = "npm", 11 | Yarn = "yarn", 12 | Pnpm = "pnpm", 13 | } 14 | 15 | export interface InstallCommand { 16 | directory: string; 17 | prettyDirectory: string; 18 | tool: InstallTool; 19 | arguments: readonly string[]; 20 | } 21 | 22 | /** 23 | * Traverses the given directory and returns a list of commands that can be used, in order, to install 24 | * the packages required for building. 25 | */ 26 | export async function installPackages(repoDir: string, ignoreScripts: boolean, quietOutput: boolean, recursiveSearch: boolean, monorepoPackages?: readonly string[], types?: string[]): Promise { 27 | monorepoPackages = monorepoPackages ?? await utils.getMonorepoOrder(repoDir); 28 | 29 | const repoName = path.basename(repoDir); 30 | 31 | const isRepoYarn = await utils.exists(path.join(repoDir, "yarn.lock")); 32 | // The existence of .yarnrc.yml indicates that this repo uses yarn 2 33 | const isRepoYarn2 = await utils.exists(path.join(repoDir, ".yarnrc.yml")); 34 | const isRepoPnpm = await utils.exists(path.join(repoDir, "pnpm-lock.yaml")); 35 | 36 | const commands: InstallCommand[] = []; 37 | 38 | const globPattern = recursiveSearch ? "**/package.json" : "package.json"; 39 | const packageFiles = utils.glob(repoDir, globPattern); 40 | 41 | for (const packageFile of packageFiles) { 42 | let inMonorepoPackageDir = false; 43 | for (const monorepoPackage of monorepoPackages) { 44 | if (inMonorepoPackageDir = packageFile.startsWith(monorepoPackage)) break; 45 | } 46 | if (inMonorepoPackageDir) { 47 | // Skipping installation of monorepo package 48 | continue; 49 | } 50 | 51 | // CONSIDER: If we're ignoring scripts, there are lerna packages, and we're not 52 | // using yarn workspaces, we might want to `lerna bootstrap`. In practice, 53 | // this has not proven to be necessary, since this combination is uncommon. 54 | 55 | const packageRoot = path.dirname(packageFile); 56 | 57 | // Heuristic, these are rarely valuable and often fail. 58 | if (/fixtures?/i.test(packageRoot)) { 59 | continue; 60 | } 61 | 62 | let tool: InstallTool; 63 | let args: string[]; 64 | 65 | const isProjectYarn2 = isRepoYarn2 || await utils.exists(path.join(packageRoot, ".yarnrc.yml")); 66 | if (isProjectYarn2 || 67 | await utils.exists(path.join(packageRoot, "yarn.lock")) || 68 | (isRepoYarn && !(await utils.exists(path.join(packageRoot, "package-lock.json"))))) { 69 | tool = InstallTool.Yarn; 70 | 71 | // Yarn 2 dropped support for most `install` arguments 72 | if (isProjectYarn2) { 73 | args = ["install", "--no-immutable"] 74 | 75 | if (ignoreScripts) { 76 | // TODO: this seems to be called --skip-build in yarn 3 - we might want to try to distinguish 77 | args.push("--mode=skip-build"); 78 | } 79 | } 80 | else { 81 | args = ["install", "--ignore-engines"]; 82 | 83 | if (ignoreScripts) { 84 | args.push("--ignore-scripts"); 85 | } 86 | 87 | if (quietOutput) { 88 | args.push("--silent"); 89 | } 90 | } 91 | } 92 | else if (isRepoPnpm || await utils.exists(path.join(packageRoot, "pnpm-lock.yaml"))) { 93 | tool = InstallTool.Pnpm; 94 | args = ["install", "--no-frozen-lockfile", "--prefer-offline"]; 95 | 96 | if (ignoreScripts) { 97 | args.push("--ignore-scripts"); 98 | } 99 | 100 | if (quietOutput) { 101 | args.push("--reporter=silent"); 102 | } 103 | 104 | } 105 | else if (await utils.exists(path.join(packageRoot, "package.json"))) { 106 | tool = InstallTool.Npm; 107 | 108 | const haveLock = await utils.exists(path.join(packageRoot, "package-lock.json")) || 109 | await hasCurrentShrinkwrap(packageRoot); 110 | 111 | args = [haveLock ? "ci" : "install", "--prefer-offline", "--no-audit", "--no-progress", "--legacy-peer-deps"]; 112 | 113 | if (ignoreScripts) { 114 | args.push("--ignore-scripts"); 115 | } 116 | 117 | if (quietOutput) { 118 | args.push("-q"); 119 | } 120 | } 121 | else { 122 | continue; 123 | } 124 | 125 | const prettyDirectory = path.join(repoName, path.relative(repoDir, packageRoot)); 126 | 127 | commands.push({ 128 | directory: packageRoot, 129 | prettyDirectory, 130 | tool, 131 | arguments: args, 132 | }); 133 | 134 | if (types && types.length > 0) { 135 | // `types` is only present for user tests and all known user tests use npm, not yarn 136 | // Besides, we're using --no-save, so it shouldn't matter which tool we use 137 | const typesPackageNames = types.map(t => `@types/${t}`); 138 | const args = ["install", ...typesPackageNames, "--no-save", "--ignore-scripts", "--legacy-peer-deps"]; 139 | 140 | commands.push({ 141 | directory: packageRoot, 142 | prettyDirectory, 143 | tool: InstallTool.Npm, 144 | arguments: args 145 | }); 146 | } 147 | } 148 | 149 | return commands; 150 | } 151 | 152 | async function hasCurrentShrinkwrap(packageRoot: string): Promise { 153 | if (!utils.exists(path.join(packageRoot, "npm-shrinkwrap.json"))) { 154 | return false; 155 | } 156 | 157 | try { 158 | const contents = await fs.promises.readFile(path.join(packageRoot, "npm-shrinkwrap.json"), { encoding: "utf-8" }); 159 | const value = json5.parse(contents); 160 | return +value.lockfileVersion >= 1; 161 | } 162 | catch { 163 | return false; 164 | } 165 | } 166 | 167 | -------------------------------------------------------------------------------- /src/utils/overlayFS.ts: -------------------------------------------------------------------------------- 1 | import fs = require("fs"); 2 | import path = require("path"); 3 | import { execAsync } from "./execUtils"; 4 | 5 | export interface OverlayBaseFS { 6 | path: string; 7 | createOverlay(): Promise; 8 | } 9 | 10 | export interface OverlayMergedFS extends AsyncDisposable { 11 | path: string; 12 | } 13 | 14 | export interface DisposableOverlayBaseFS extends OverlayBaseFS, AsyncDisposable {} 15 | 16 | // @ts-ignore 17 | Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose"); 18 | 19 | const processCwd = process.cwd(); 20 | 21 | /** 22 | * Creates an overlay FS using a tmpfs mount. A base directory is created on the tmpfs. 23 | * New overlays are created by mounting an overlay on top of the base directory. 24 | * 25 | * This requires root access. 26 | */ 27 | export async function createTempOverlayFS(root: string, diagnosticOutput: boolean): Promise { 28 | await tryUnmount(root); 29 | await rmWithRetryAsRoot(root); 30 | await mkdirAllAsRoot(root); 31 | await execAsync(processCwd, `sudo mount -t tmpfs tmpfs ${root}`); 32 | 33 | const lowerDir = path.join(root, "base"); 34 | await mkdirAll(lowerDir); 35 | 36 | let overlay: OverlayMergedFS | undefined; 37 | 38 | async function createOverlay(): Promise { 39 | if (overlay) { 40 | throw new Error("Overlay has already been created"); 41 | } 42 | 43 | // Using short names here as these paths can appear in the summaries. 44 | const overlayRoot = path.join(root, "_"); 45 | await rmWithRetryAsRoot(overlayRoot); 46 | 47 | const upperDir = path.join(overlayRoot, ".u"); 48 | const workDir = path.join(overlayRoot, ".w"); 49 | const merged = path.join(overlayRoot, "m"); 50 | 51 | await mkdirAll(upperDir, workDir, merged); 52 | 53 | if (diagnosticOutput) { 54 | await diskUsageRoot(lowerDir); 55 | await diskUsageRoot(overlayRoot); 56 | } 57 | 58 | await execAsync(processCwd, `sudo mount -t overlay overlay -o lowerdir=${lowerDir},upperdir=${upperDir},workdir=${workDir} ${merged}`); 59 | 60 | overlay = { 61 | path: merged, 62 | [Symbol.asyncDispose]: async () => { 63 | overlay = undefined; 64 | if (diagnosticOutput) { 65 | await diskUsageRoot(upperDir); 66 | } 67 | await tryUnmount(merged); 68 | await rmWithRetryAsRoot(overlayRoot); 69 | } 70 | } 71 | 72 | return overlay; 73 | } 74 | 75 | return { 76 | path: lowerDir, 77 | createOverlay, 78 | [Symbol.asyncDispose]: async () => { 79 | if (diagnosticOutput) { 80 | await diskUsageRoot(root); 81 | } 82 | if (overlay) { 83 | await overlay[Symbol.asyncDispose](); 84 | } 85 | await tryUnmount(root); 86 | await rmWithRetryAsRoot(root); 87 | }, 88 | } 89 | } 90 | 91 | async function retry(fn: (() => void) | (() => Promise), retries: number, delayMs: number): Promise { 92 | for (let i = 0; i < retries; i++) { 93 | try { 94 | await fn(); 95 | return; 96 | } catch (e) { 97 | if (i === retries - 1) { 98 | throw e; 99 | } 100 | await new Promise(resolve => setTimeout(resolve, delayMs)); 101 | } 102 | } 103 | } 104 | 105 | async function tryUnmount(p: string) { 106 | if (!fs.existsSync(p)) return; 107 | try { 108 | await retry(async () => { 109 | try { 110 | await execAsync(processCwd, `sudo umount -R ${p}`); 111 | } catch (e) { 112 | // Kill processes using the mount. 113 | try { 114 | await execAsync(processCwd, `sudo fuser -vkm ${p}`); 115 | } catch { 116 | // This command will exit with a non-zero exit code on no handles; ignore. 117 | } 118 | // Ensure we retry. 119 | throw e; 120 | } 121 | }, 3, 1000) 122 | } catch { 123 | // Print out the remaining processes for debugging. 124 | try { 125 | await execAsync(processCwd, `sudo fuser -vm ${p}`); 126 | } catch { 127 | // This command will exit with a non-zero exit code on no handles; ignore. 128 | } 129 | } 130 | } 131 | 132 | function diskUsageRoot(p: string) { 133 | return execAsync(processCwd, `sudo du -sh ${p}`); 134 | } 135 | 136 | function rmWithRetry(p: string) { 137 | return retry(() => execAsync(processCwd, `rm -rf ${p}`), 3, 1000); 138 | } 139 | 140 | function rmWithRetryAsRoot(p: string) { 141 | return retry(() => execAsync(processCwd, `sudo rm -rf ${p}`), 3, 1000); 142 | } 143 | 144 | function mkdirAll(...args: string[]) { 145 | return execAsync(processCwd, `mkdir -p ${args.join(" ")}`); 146 | } 147 | 148 | function mkdirAllAsRoot(...args: string[]) { 149 | return execAsync(processCwd, `sudo mkdir -p ${args.join(" ")}`); 150 | } 151 | 152 | /** 153 | * Creates a fake overlay FS, which is just a directory on the local filesystem. 154 | * Overlays are created by copying the contents of the `base` directory. 155 | */ 156 | export async function createCopyingOverlayFS(root: string, _diagnosticOutput: boolean): Promise { 157 | await rmWithRetry(root); 158 | await mkdirAll(root); 159 | 160 | const basePath = path.join(root, "base"); 161 | await mkdirAll(basePath); 162 | 163 | let overlay: OverlayMergedFS | undefined; 164 | 165 | async function createOverlay(): Promise { 166 | if (overlay) { 167 | throw new Error("Overlay has already been created"); 168 | } 169 | 170 | const overlayRoot = path.join(root, "overlay"); 171 | await rmWithRetry(overlayRoot); 172 | 173 | await execAsync(processCwd, `cp -r --reflink=auto ${basePath} ${overlayRoot}`); 174 | 175 | overlay = { 176 | path: overlayRoot, 177 | [Symbol.asyncDispose]: async () => { 178 | overlay = undefined; 179 | await rmWithRetry(overlayRoot); 180 | } 181 | } 182 | 183 | return overlay; 184 | } 185 | 186 | return { 187 | path: basePath, 188 | createOverlay, 189 | [Symbol.asyncDispose]: async () => { 190 | if (overlay) { 191 | await overlay[Symbol.asyncDispose](); 192 | overlay = undefined; 193 | } 194 | await rmWithRetry(root); 195 | }, 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/utils/gitUtils.ts: -------------------------------------------------------------------------------- 1 | import octokit = require("@octokit/rest"); 2 | import { execAsync } from "./execUtils"; 3 | import utils = require("./packageUtils"); 4 | import fs = require("fs"); 5 | import path = require("path"); 6 | 7 | // The bundled types don't work with CJS imports 8 | import { simpleGit as git } from "simple-git"; 9 | 10 | export interface Repo { 11 | name: string; 12 | url?: string; 13 | owner?: string; 14 | types?: string[]; 15 | branch?: string; 16 | } 17 | 18 | const repoProperties = { 19 | owner: "microsoft", 20 | repo: "typescript", 21 | }; 22 | 23 | export async function getPopularRepos(language = "TypeScript", count = 100, repoStartIndex = 0, skipRepos?: string[], cachePath?: string): Promise { 24 | const cacheEncoding = { encoding: "utf-8" } as const; 25 | 26 | if (cachePath && await utils.exists(cachePath)) { 27 | const contents = await fs.promises.readFile(cachePath, cacheEncoding); 28 | const cache: Repo[] = JSON.parse(contents); 29 | if (cache.length >= count) { 30 | return cache.slice(0, count); 31 | } 32 | } 33 | 34 | const kit = new octokit.Octokit({ 35 | auth: process.env.GITHUB_PAT, 36 | }); 37 | const perPage = Math.min(100, count + (skipRepos?.length ?? 0)); 38 | 39 | let repos: Repo[] = []; 40 | for (let page = 1; repos.length < count; page++) { 41 | const response = await kit.search.repos({ 42 | q: `language:${language}+stars:>100 archived:no`, 43 | sort: "stars", 44 | order: "desc", 45 | per_page: perPage, 46 | page, 47 | }); 48 | 49 | let items = response.data.items; 50 | if (repoStartIndex > 0) { 51 | if (repoStartIndex < items.length) { 52 | items = items.slice(repoStartIndex); 53 | repoStartIndex = 0; 54 | } 55 | else { 56 | repoStartIndex -= items.length; 57 | continue; 58 | } 59 | } 60 | 61 | if (response.status !== 200) throw response; 62 | 63 | for (const repo of items) { 64 | if (!skipRepos?.includes(repo.html_url)) { 65 | repos.push({ url: repo.html_url, name: repo.name, owner: repo.owner?.login }); 66 | if (repos.length >= count) { 67 | break; 68 | } 69 | } 70 | } 71 | 72 | if (!response.headers.link || !response.headers.link.includes('rel="next"')) break; 73 | } 74 | 75 | if (cachePath) { 76 | await fs.promises.writeFile(cachePath, JSON.stringify(repos), cacheEncoding); 77 | } 78 | 79 | return repos; 80 | } 81 | 82 | export async function cloneRepoIfNecessary(parentDir: string, repo: Repo): Promise { 83 | if (!repo.url) { 84 | throw new Error("Repo url cannot be `undefined`"); 85 | } 86 | 87 | const repoDir = path.join(parentDir, repo.name); 88 | if (!await utils.exists(repoDir)) { 89 | console.log(`Cloning ${repo.url} into ${repoDir}`); 90 | 91 | let options = ["--recurse-submodules", "--depth=1"]; 92 | if (repo.branch) { 93 | options.push(`--branch=${repo.branch}`); 94 | } 95 | 96 | await git(parentDir).clone(repo.url, repo.name, options); 97 | } 98 | } 99 | 100 | type Result = { 101 | body: string, 102 | owner: string, 103 | repo: string, 104 | } 105 | export type GitResult = Result & { kind: 'git', title: string } 106 | export type UserResult = Result & { kind: 'user', issue_number: number } 107 | 108 | export async function createIssue(postResult: boolean, title: string, bodyChunks: readonly string[], sawNewErrors: boolean): Promise { 109 | const issue = { 110 | ...repoProperties, 111 | title, 112 | body: bodyChunks[0], 113 | }; 114 | 115 | const additionalComments = bodyChunks.slice(1).map(chunk => ({ 116 | ...repoProperties, 117 | body: chunk, 118 | })); 119 | 120 | if (!postResult) { 121 | console.log("Issue not posted: "); 122 | console.log(JSON.stringify(issue)); 123 | for (const comment of additionalComments) { 124 | console.log(JSON.stringify(comment)); 125 | } 126 | return { kind: 'git', ...issue }; 127 | } 128 | 129 | console.log("Creating a summary issue"); 130 | 131 | const kit = new octokit.Octokit({ 132 | auth: process.env.GITHUB_PAT, 133 | }); 134 | 135 | const created = await kit.issues.create(issue); 136 | 137 | const issueNumber = created.data.number; 138 | console.log(`Created issue #${issueNumber}: ${created.data.html_url}`); 139 | 140 | const maxCommentLength = 65535; 141 | const tooLongFooter = `\n\nThis comment was too long to display in full; see the build artifact for the full details.`; 142 | 143 | for (const comment of additionalComments) { 144 | let body = comment.body; 145 | if (body.length > maxCommentLength) { 146 | body = body.slice(0, maxCommentLength - tooLongFooter.length) + tooLongFooter; 147 | } 148 | 149 | await kit.issues.createComment({ issue_number: issueNumber, ...comment, body }); 150 | } 151 | 152 | if (!sawNewErrors) { 153 | await kit.issues.update({ 154 | ...repoProperties, 155 | issue_number: issueNumber, 156 | state: "closed", 157 | }); 158 | } 159 | } 160 | 161 | export async function createComment(prNumber: number, statusComment: number, distinctId: string, postResult: boolean, bodyChunks: readonly string[], somethingChanged: boolean): Promise { 162 | const newComments = bodyChunks.map(body => ({ 163 | ...repoProperties, 164 | issue_number: prNumber, 165 | body, 166 | })); 167 | 168 | // Occasionally, GH posting fails, so it helps to dump the comment 169 | console.log("GH comment(s): "); 170 | console.log(JSON.stringify(newComments, undefined, " ")); 171 | 172 | if (!postResult) { 173 | return; 174 | } 175 | 176 | console.log("Posting github comment(s)"); 177 | 178 | const kit = new octokit.Octokit({ 179 | auth: process.env.GITHUB_PAT, 180 | }); 181 | 182 | const newCommentUrls: string[] = []; 183 | 184 | for (const newComment of newComments) { 185 | const response = await kit.issues.createComment(newComment); 186 | 187 | const newCommentUrl = response.data.html_url; 188 | console.log(`Created comment #${response.data.id}: ${newCommentUrl}`); 189 | 190 | newCommentUrls.push(newCommentUrl); 191 | } 192 | 193 | 194 | const emoji = !somethingChanged ? "✅" : "👀"; 195 | 196 | const toReplace = ``; 197 | let posted = false; 198 | for (let i = 0; i < 5; i++) { 199 | // Get status comment contents 200 | const statusCommentResp = await kit.issues.getComment({ 201 | comment_id: statusComment, 202 | owner: "Microsoft", 203 | repo: "TypeScript", 204 | }); 205 | 206 | const oldComment = statusCommentResp.data.body; 207 | if (!oldComment?.includes(toReplace)) { 208 | posted = true; 209 | break; 210 | } 211 | 212 | const newComment = oldComment.replace( 213 | toReplace, 214 | `[${emoji} Results](${newCommentUrls[0]})`, 215 | ); 216 | 217 | // Update status comment 218 | await kit.issues.updateComment({ 219 | comment_id: statusComment, 220 | owner: "Microsoft", 221 | repo: "TypeScript", 222 | body: newComment, 223 | }); 224 | 225 | // Repeat; someone may have edited the comment at the same time. 226 | await new Promise(resolve => setTimeout(resolve, 1000)); 227 | } 228 | } 229 | 230 | export async function checkout(cwd: string, branch: string) { 231 | await execAsync(cwd, `git fetch origin ${branch}:${branch} --recurse-submodules --depth=1`); 232 | await execAsync(cwd, `git checkout ${branch}`); 233 | } 234 | -------------------------------------------------------------------------------- /src/utils/projectGraph.ts: -------------------------------------------------------------------------------- 1 | import fs = require("fs"); 2 | import json5 = require("json5"); 3 | import path = require("path"); 4 | import utils = require("./packageUtils"); 5 | 6 | export interface Project { 7 | path: string, 8 | hasParseError: boolean, 9 | hasExtensionError: boolean, 10 | hasReferenceError: boolean, 11 | isComposite: boolean, 12 | extends: /*readonly*/ Project[], 13 | extendedBy: /*readonly*/ Project[], 14 | references: /*readonly*/ Project[], 15 | referencedBy: /*readonly*/ Project[] 16 | } 17 | 18 | export interface ProjectsToBuild { 19 | /** Order matters */ 20 | simpleProjects: readonly Project[], 21 | /** Order matters */ 22 | rootCompositeProjects: readonly Project[], 23 | hasError: boolean, 24 | } 25 | 26 | function resolvePath(...pathSegments: readonly string[]): string { 27 | const resolved = path.resolve(...pathSegments); 28 | return resolved.replace(/\\/g, "/"); 29 | } 30 | 31 | function getFileNameFromProjectName(projectName: string): string { 32 | return projectName.endsWith(".json") 33 | ? projectName 34 | : path.basename(projectName).match(/tsconfig/) 35 | ? projectName + ".json" 36 | : path.join(projectName, "tsconfig.json"); 37 | } 38 | 39 | /** 40 | * Note that the returned projects are ordered in monorepo scenarios - 41 | * they should be built in the order in which they are returned. 42 | */ 43 | async function getProjectPaths(repoDir: string, monorepoOrder: readonly string[]): Promise { 44 | const projectPaths = []; 45 | const seen = new Set(); 46 | 47 | for (const monorepoDir of monorepoOrder) { 48 | for (const path of (await utils.glob(monorepoDir, "**/*tsconfig*.json"))) { 49 | if (!seen.has(path)) { 50 | seen.add(path); 51 | projectPaths.push(path); 52 | } 53 | } 54 | } 55 | 56 | for (const path of (await utils.glob(repoDir, "**/*tsconfig*.json"))) { 57 | if (!seen.has(path)) { 58 | seen.add(path); 59 | projectPaths.push(path); 60 | } 61 | } 62 | 63 | return projectPaths; 64 | } 65 | 66 | function dependsOnProjectWithError(project: Project, ignoreExtensionErrors: boolean): boolean { 67 | const stack = [ project ]; 68 | const seen = new Set(); 69 | 70 | while (stack.length) { 71 | const curr = stack.pop()!; 72 | 73 | if (seen.has(curr)) { 74 | continue; 75 | } 76 | seen.add(curr); 77 | 78 | if (curr.hasParseError || curr.hasReferenceError|| (!ignoreExtensionErrors && curr.hasExtensionError)) { 79 | return true; 80 | } 81 | 82 | stack.push(...curr.references); 83 | stack.push(...curr.extends); 84 | } 85 | 86 | return false; 87 | } 88 | 89 | /** 90 | * Heuristically, returns a collection of projects that should be built (excluding, for example, downstream and base projects). 91 | * Note: Providing a list of monorepoPackages is a performance optimization - they'll be computed otherwise. 92 | */ 93 | export async function getProjectsToBuild(repoDir: string, monorepoPackages?: readonly string[], ignoreExtensionErrors: boolean = true): Promise { 94 | monorepoPackages = monorepoPackages ?? await utils.getMonorepoOrder(repoDir); 95 | 96 | const projectPaths = await getProjectPaths(repoDir, monorepoPackages); 97 | 98 | const projectMap = new Map(); // path to data 99 | for (const projectPath of projectPaths) { 100 | projectMap.set(projectPath, 101 | { 102 | path: projectPath, 103 | hasParseError: false, 104 | hasExtensionError: false, 105 | hasReferenceError: false, 106 | isComposite: false, 107 | extends: [], 108 | extendedBy: [], 109 | references: [], 110 | referencedBy: [] 111 | }); 112 | } 113 | 114 | const projectsWithCompositeFlag: Project[] = []; 115 | 116 | for (const projectPath of projectPaths) { 117 | const project = projectMap.get(projectPath)!; 118 | 119 | let config: any = {}; 120 | try { 121 | const contents = fs.readFileSync(projectPath, { encoding: "utf-8" }); 122 | // If the tsconfig is empty, stick with {} 123 | if (contents.trim().length > 0) { 124 | config = json5.parse(contents); 125 | } 126 | } 127 | catch { 128 | project.hasParseError = true; 129 | continue; 130 | } 131 | 132 | const projectDir = path.dirname(projectPath); 133 | 134 | if (config.compilerOptions && config.compilerOptions.composite) { 135 | projectsWithCompositeFlag.push(project); 136 | } 137 | 138 | if (config.extends) { 139 | const extendedPath = resolvePath(projectDir, getFileNameFromProjectName(config.extends)); 140 | if (projectMap.has(extendedPath)) { 141 | const extendedProject = projectMap.get(extendedPath)!; 142 | project.extends.push(extendedProject); 143 | extendedProject.extendedBy.push(project); 144 | } 145 | else { 146 | project.hasExtensionError = true; 147 | } 148 | } 149 | 150 | if (config.references) { 151 | for (const reference of config.references) { 152 | const referencedPath = resolvePath(projectDir, getFileNameFromProjectName(reference.path)); 153 | if (projectMap.has(referencedPath)) { 154 | const referencedProject = projectMap.get(referencedPath)!; 155 | project.references.push(referencedProject); 156 | referencedProject.referencedBy.push(project); 157 | } 158 | else { 159 | project.hasReferenceError = true; 160 | } 161 | } 162 | } 163 | } 164 | 165 | for (const project of projectsWithCompositeFlag) { 166 | if (!project.extendedBy.length) { 167 | project.isComposite = true; 168 | continue; 169 | } 170 | 171 | const stack: Project[] = [ project ]; 172 | while (stack.length) { 173 | const curr = stack.pop()!; 174 | 175 | if (curr.isComposite) { 176 | continue; 177 | } 178 | curr.isComposite = true; 179 | 180 | stack.push(...curr.extendedBy); 181 | } 182 | } 183 | 184 | const simpleProjects: Project[] = []; 185 | const rootCompositeProjects: Project[] = []; 186 | let hasError = false; 187 | for (const projectPath of projectPaths) { 188 | const project = projectMap.get(projectPath)!; 189 | 190 | if (project.referencedBy.length) { 191 | // Should be built by the upstream project 192 | continue; 193 | } 194 | 195 | if (project.isComposite || project.references.length) { 196 | // Composite project 197 | 198 | if (dependsOnProjectWithError(project, ignoreExtensionErrors)) { 199 | // Can't trust results if one of the project files is bad 200 | hasError = true; 201 | continue; 202 | } 203 | 204 | rootCompositeProjects.push(project); 205 | } 206 | else { 207 | // Simple project 208 | 209 | if (project.hasParseError || (!ignoreExtensionErrors && project.hasExtensionError)) { 210 | hasError = true; 211 | continue; 212 | } 213 | 214 | // Sometimes, source configs are extended by test configs and do need to be built. 215 | // Sometimes, base configs neglect to explicitly drop all inputs and should not be built. 216 | // As a heuristic, build the ones with simple names. 217 | if (project.extendedBy.length && !path.basename(projectPath).match(/^[tj]sconfig.json$/)) { 218 | continue; 219 | } 220 | 221 | simpleProjects.push(project); 222 | } 223 | } 224 | 225 | return { 226 | simpleProjects, 227 | rootCompositeProjects, 228 | hasError 229 | }; 230 | } -------------------------------------------------------------------------------- /test/main.test.ts: -------------------------------------------------------------------------------- 1 | import { getTscRepoResult, downloadTsRepoAsync, mainAsync } from '../src/main' 2 | import { execSync } from "child_process" 3 | import path = require("path") 4 | import { createCopyingOverlayFS } from '../src/utils/overlayFS' 5 | import { SpawnResult } from '../src/utils/execUtils'; 6 | 7 | jest.mock('random-seed', () => ({ 8 | create: () => { 9 | return { 10 | random: () => 1, 11 | seed: () => { }, 12 | string: () => '' 13 | }; 14 | }, 15 | })); 16 | jest.mock("../src/utils/packageUtils", () => ({ 17 | exists: jest.fn().mockResolvedValue(true), 18 | getMonorepoOrder: jest.fn().mockResolvedValue([ 19 | "./dirA/package.json", 20 | "./dirB/dirC/package.json", 21 | "./dirD/DirE/dirF/package.json" 22 | ]) 23 | })); 24 | jest.mock("../src/utils/execUtils", () => ({ 25 | spawnWithTimeoutAsync: jest.fn((cwd: string, command: string, args: readonly string[], timeoutMs: number, env?: {}) => { 26 | if (command === 'npm') { 27 | // Return nothing so that npm install appears successfull. 28 | return {}; 29 | } 30 | 31 | return typeScriptSpawnResult(args); 32 | }), 33 | execAsync: async (cwd: string, command: string) => { 34 | if (command.startsWith('npm pack typescript@latest')) { 35 | return ' typescript-0.0.0.tgz'; 36 | } else if (command.startsWith('npm pack typescript@next')) { 37 | return ' typescript-1.1.1.tgz'; 38 | } else if (command.startsWith('git rev-parse')) { 39 | return '57b462387e88aa7e363af0daf867a5dc1e83a935'; 40 | } 41 | 42 | return ''; 43 | } 44 | 45 | })); 46 | jest.mock('fs', () => ({ 47 | promises: { 48 | writeFile: jest.fn(), 49 | copyFile: jest.fn(), 50 | rename: jest.fn().mockResolvedValue(undefined), 51 | }, 52 | readFileSync: (path: string) => { 53 | if (path.endsWith("replay.txt")) { 54 | return '{\"rootDirPlaceholder\":\"@PROJECT_ROOT@\",\"serverArgs\":[\"--disableAutomaticTypingAcquisition\"]}\r\n{\"seq\":1,\"type\":\"request\",\"command\":\"configure\",\"arguments\":{\"preferences\":{\"disableLineTextInReferences\":true,\"includePackageJsonAutoImports\":\"auto\",\"includeCompletionsForImportStatements\":true,\"includeCompletionsWithSnippetText\":true,\"includeAutomaticOptionalChainCompletions\":true,\"includeCompletionsWithInsertText\":true,\"includeCompletionsWithClassMemberSnippets\":true,\"allowIncompleteCompletions\":true,\"includeCompletionsForModuleExports\":false},\"watchOptions\":{\"excludeDirectories\":[\"**/node_modules\"]}}}\r\n{\"seq\":2,\"type\":\"request\",\"command\":\"updateOpen\",\"arguments\":{\"changedFiles\":[],\"closedFiles\":[],\"openFiles\":[{\"file\":\"@PROJECT_ROOT@/sample_repoName.config.js\",\"projectRootPath\":\"@PROJECT_ROOT@\"}]}}\r\n{\"seq\":3,\"type\":\"request\",\"command\":\"cursedCommand\",\"arguments\":{\"file\":\"@PROJECT_ROOT@/src/sampleTsFile.ts\",\"line\":1,\"offset\":1,\"includeExternalModuleExports\":false,\"triggerKind\":1}}';; 55 | 56 | } else if (path.endsWith('repos.json')) { 57 | return JSON.stringify([{ 58 | "url": "https://github.com/MockRepoOwner/MockRepoName", 59 | "name": "MockRepoName", 60 | "owner": "MockRepoOwner" 61 | }]); 62 | } 63 | } 64 | })); 65 | jest.mock('../src/utils/installPackages', () => { 66 | const actualIp = jest.requireActual('../src/utils/installPackages') 67 | const npmCommand = { 68 | tool: 'npm', 69 | arguments: [ 70 | 'install', 71 | '--prefer-offline', 72 | '--no-audit', 73 | '--no-progress', 74 | '--legacy-peer-deps', 75 | '--ignore-scripts', 76 | '-q', 77 | ] 78 | }; 79 | 80 | return { 81 | InstallTool: actualIp.InstallTool, 82 | installPackages: async () => { 83 | return [ 84 | { 85 | ...npmCommand, 86 | directory: '/mnt/repos/dirA', 87 | prettyDirectory: 'dirA', 88 | }, 89 | { 90 | ...npmCommand, 91 | directory: '/mnt/repos/dirB/dirC', 92 | prettyDirectory: 'dirB/dirC', 93 | }, 94 | { 95 | ...npmCommand, 96 | directory: '/mnt/repos/dirD/dirE/dirF', 97 | prettyDirectory: 'dirD/dirE/dirF', 98 | } 99 | ] 100 | } 101 | } 102 | }); 103 | 104 | let typeScriptSpawnResult: (args: readonly string[]) => SpawnResult; 105 | 106 | const errorStdout = JSON.stringify({ 107 | "request_seq": "123", 108 | "command": "cursedCommand", 109 | "message": "Some error. Could not do something. \nMaybe a Debug fail.\n at a (/mnt/vss/_work/1/s/typescript-1.1.1/lib/typescript.js:1:1)\n at b (/mnt/vss/_work/1/s/typescript-1.1.1/lib/typescript.js:2:2)\n at c (/mnt/vss/_work/1/s/typescript-1.1.1/lib/typescript.js:3:3)\n at d (/mnt/vss/_work/1/s/typescript-1.1.1/lib/typescript.js:4:4)\n at e (/mnt/vss/_work/1/s/typescript-1.1.1/lib/typescript.js:5:5)" 110 | }); 111 | 112 | describe("main", () => { 113 | jest.setTimeout(10 * 60 * 1000); 114 | 115 | xit("build-only correctly caches", async () => { 116 | const { status, summary } = await getTscRepoResult( 117 | { 118 | name: "TypeScript-Node-Starter", 119 | url: "https://github.com/Microsoft/TypeScript-Node-Starter.git" 120 | }, 121 | "./userTests", 122 | path.resolve("./typescript-main/built/local/tsc.js"), 123 | path.resolve("./typescript-44585/built/local/tsc.js"), 124 | /*ignoreOldTscFailures*/ true, // as in a user test 125 | await createCopyingOverlayFS("./ts_downloads", false), 126 | /*diagnosticOutput*/ false) 127 | expect(status).toEqual("NewBuildHadErrors") 128 | expect(summary).toBeDefined() 129 | expect(summary!.startsWith(`# [TypeScript-Node-Starter](https://github.com/Microsoft/TypeScript-Node-Starter.git)`)).toBeTruthy() 130 | expect(summary!.includes("- \`error TS2496: The 'arguments' object cannot be referenced in an arrow function in ES3 and ES5. Consider using a standard function expression.\`")).toBeTruthy() 131 | }); 132 | 133 | it("downloads from a branch", async () => { 134 | const actualFs = jest.requireActual('fs'); 135 | 136 | if (!actualFs.existsSync("./testDownloads/main")) { 137 | actualFs.mkdirSync("./testDownloads/main", { recursive: true }); 138 | } 139 | else if (actualFs.existsSync("./testDownloads/main/typescript-test-fake-error")) { 140 | execSync("cd ./testDownloads/main/typescript-test-fake-error && git restore . && cd ..") 141 | } 142 | await downloadTsRepoAsync('./testDownloads/main', 'https://github.com/sandersn/typescript', 'test-fake-error', 'tsc') 143 | }); 144 | 145 | it("outputs server errors", async () => { 146 | const mockedFs = require('fs'); 147 | 148 | typeScriptSpawnResult = () => ({ 149 | stdout: errorStdout, 150 | stderr: '', 151 | code: 5, 152 | signal: null, 153 | 154 | }); 155 | 156 | await mainAsync({ 157 | testType: "github", 158 | tmpfs: false, 159 | entrypoint: 'tsserver', 160 | diagnosticOutput: false, 161 | buildWithNewWhenOldFails: false, 162 | repoListPath: "./artifacts/repos.json", 163 | workerCount: 1, 164 | workerNumber: 1, 165 | oldTsNpmVersion: 'latest', 166 | newTsNpmVersion: 'next', 167 | resultDirName: 'RepoResults123', 168 | prngSeed: 'testSeed', 169 | }); 170 | 171 | // Remove all references to the base path so that snapshot pass successfully. 172 | mockedFs.promises.writeFile.mock.calls.forEach((e: [string, string]) => { 173 | e[0] = e[0].replace(process.cwd(), ""); 174 | }); 175 | 176 | expect(mockedFs.promises.writeFile).toMatchSnapshot(); 177 | }); 178 | 179 | it("outputs old server errors", async () => { 180 | const mockedFs = require('fs'); 181 | 182 | typeScriptSpawnResult = args => { 183 | let isOldServer = args.some(x => x.includes('0.0.0')); 184 | 185 | // Only "old" reports an error. 186 | return isOldServer ? { 187 | stdout: errorStdout, 188 | stderr: '', 189 | code: 5, 190 | signal: null, 191 | } : { 192 | stdout: '', 193 | stderr: '', 194 | code: 0, 195 | signal: null, 196 | }; 197 | 198 | }; 199 | 200 | await mainAsync({ 201 | testType: "github", 202 | tmpfs: false, 203 | entrypoint: 'tsserver', 204 | diagnosticOutput: false, 205 | buildWithNewWhenOldFails: false, 206 | repoListPath: "./artifacts/repos.json", 207 | workerCount: 1, 208 | workerNumber: 1, 209 | oldTsNpmVersion: 'latest', 210 | newTsNpmVersion: 'next', 211 | resultDirName: 'RepoResults123', 212 | prngSeed: 'testSeed', 213 | }); 214 | 215 | // Remove all references to the base path so that snapshot pass successfully. 216 | mockedFs.promises.writeFile.mock.calls.forEach((e: [string, string]) => { 217 | e[0] = e[0].replace(process.cwd(), ""); 218 | }); 219 | 220 | expect(mockedFs.promises.writeFile).toMatchSnapshot(); 221 | }); 222 | }) 223 | -------------------------------------------------------------------------------- /src/utils/getTscErrors.ts: -------------------------------------------------------------------------------- 1 | import ghLink = require("@typescript/github-link"); 2 | import projectGraph = require("./projectGraph"); 3 | import { SpawnResult, spawnWithTimeoutAsync } from "./execUtils"; 4 | import fs = require("fs"); 5 | import path = require("path"); 6 | 7 | const nodePath = process.argv0; 8 | 9 | const errorCodeRegex = /error TS(\d+).*/; 10 | const errorPositionRegex = /^([^(]+)(?:\((\d+),(\d+))/; 11 | const beginProjectRegex = /Building project '([^']+)'/; 12 | 13 | interface LocalError { 14 | projectUrl: string, 15 | code: number, 16 | text: string, 17 | path?: string, 18 | lineNumber?: number, 19 | columnNumber?: number, 20 | } 21 | 22 | export interface Error { 23 | /** The URL of the containing project. May not match the one in ProjectErrors for composite projects. */ 24 | projectUrl: string, 25 | /** The URL of the containing file, including the line number. */ 26 | fileUrl?: string, 27 | /** The TypeScript error code. */ 28 | code: number, 29 | /** The first line of the error message. No structure guaranteed. */ 30 | text: string, 31 | } 32 | 33 | export interface ProjectErrors { 34 | /** The URL of the root project (i.e. the one to build to repro the errors). */ 35 | projectUrl: string, 36 | /** True if the root project is composite (i.e. if build mode was used). */ 37 | isComposite: boolean, 38 | /** True if tsc did not return success. */ 39 | hasBuildFailure: boolean, 40 | /** Errors extracted from the output of tsc. */ 41 | errors: readonly Error[], 42 | /** The verbatim output of tsc, for debugging. */ 43 | raw: string, 44 | } 45 | 46 | export interface RepoErrors { 47 | /** True if there was a problem building the project graph from the project files. */ 48 | hasConfigFailure: boolean, 49 | /** Errors, grouped by root project. */ 50 | projectErrors: readonly ProjectErrors[], 51 | } 52 | 53 | export function errorEquals(error1: Error, error2: Error) { 54 | return error1.code === error2.code 55 | && error1.fileUrl === error2.fileUrl 56 | && error1.projectUrl === error2.projectUrl 57 | && error1.text === error2.text; 58 | } 59 | 60 | /** 61 | * Given a folder and a compiler, identify buildable projects and compile them. 62 | * Assumes that package installation has already occurred. 63 | * @param repoDir Typically, the root folder of a git repository. 64 | * @param tscPath The path to tsc.js. 65 | * @param skipLibCheck True pass --skipLibCheck when building non-composite projects. (Defaults to true) 66 | */ 67 | export async function buildAndGetErrors(repoDir: string, monorepoPackages: readonly string[], isUserTestRepo: boolean, tscPath: string, timeoutMs: number, skipLibCheck: boolean = true): Promise { 68 | const projectErrors: ProjectErrors[] = []; 69 | const tsRepoPath = path.resolve(path.dirname(path.dirname(path.dirname(tscPath)))); 70 | 71 | // If it's a user test repo and there's a build.sh in the root, don't bother searching for projects 72 | // Obviously, we don't want to execute scripts we find in GH repos 73 | if (isUserTestRepo) { 74 | const buildScriptPath = path.join(repoDir, "build.sh"); 75 | if (fs.existsSync(buildScriptPath)) { 76 | 77 | const before = performance.now(); 78 | const spawnResult = await spawnWithTimeoutAsync(repoDir, path.resolve(buildScriptPath), [], timeoutMs, { ...process.env, TS: tsRepoPath }); 79 | if (!spawnResult) { 80 | throw new TimeoutError(`build.sh timed out after ${timeoutMs} ms`); 81 | } 82 | console.log(`${buildScriptPath} took ${Math.round(performance.now() - before)} ms`); 83 | 84 | const { isEmpty, stdout, hasBuildFailure } = getBuildSummary(spawnResult, /*mergeOutputs*/ true); 85 | 86 | if (!isEmpty) { 87 | projectErrors.push(await getProjectErrors(buildScriptPath, tsRepoPath, stdout, hasBuildFailure, /*isComposite*/ false, /*reportGithubLinks*/ false)); 88 | } 89 | 90 | return { 91 | hasConfigFailure: false, 92 | projectErrors, 93 | }; 94 | } 95 | } 96 | 97 | const simpleBuildArgs = ["--skipLibCheck", `${skipLibCheck}`, "--incremental", "false", "--pretty", "false", "-p"]; 98 | const compositeBuildArgs = ["-b", "-f", "-v"]; // Build mode doesn't support --skipLibCheck or --pretty 99 | 100 | const { simpleProjects, rootCompositeProjects, hasError: hasConfigFailure } = await projectGraph.getProjectsToBuild(repoDir, monorepoPackages); 101 | // TODO: Move this inside getProjectsToBuild, separating them is pointless (originally separate to enable simple-only and composite-only runs) 102 | const projectsToBuild = simpleProjects.concat(rootCompositeProjects); 103 | if (!projectsToBuild.length) { 104 | return { 105 | hasConfigFailure, 106 | projectErrors, 107 | }; 108 | } 109 | 110 | const startMs = performance.now(); 111 | for (const { path: projectPath, isComposite } of projectsToBuild) { 112 | const elapsedMs = performance.now() - startMs; 113 | 114 | const args = ["--max-old-space-size=3072", tscPath, ...(isComposite ? compositeBuildArgs : simpleBuildArgs), path.basename(projectPath)]; 115 | const spawnResult = await spawnWithTimeoutAsync(path.dirname(projectPath), nodePath, args, timeoutMs - elapsedMs); 116 | if (!spawnResult) { 117 | throw new Error(`Building ${projectPath} timed out after ${Math.round(timeoutMs - elapsedMs)} ms (of ${timeoutMs} ms for repo)`); 118 | } 119 | 120 | const { isEmpty, stdout, hasBuildFailure } = getBuildSummary(spawnResult); 121 | if (isEmpty) continue; 122 | 123 | projectErrors.push(await getProjectErrors(projectPath, tsRepoPath, stdout, hasBuildFailure, isComposite, /*reportGithubLinks*/ !isUserTestRepo)); 124 | } 125 | 126 | return { 127 | hasConfigFailure, 128 | projectErrors, 129 | }; 130 | } 131 | 132 | async function getProjectErrors(projectPath: string, tsRepoPath: string, stdout: string, hasBuildFailure: boolean, isComposite: boolean, reportGithubLinks: boolean): Promise { 133 | const projectDir = path.dirname(projectPath); 134 | const projectUrl = reportGithubLinks ? await ghLink.getGithubLink(projectPath) : projectPath; // Use project path for user tests as they don't contain a git project. 135 | 136 | let localErrors: LocalError[] = []; 137 | let currProjectUrl = projectUrl; 138 | 139 | const lines = stdout.split(/\r\n?|\n/); 140 | for (const line of lines) { 141 | const projectMatch = isComposite && line.match(beginProjectRegex); 142 | if (projectMatch) { 143 | currProjectUrl = reportGithubLinks ? await ghLink.getGithubLink(path.resolve(projectDir, projectMatch[1])) : path.resolve(projectDir, projectMatch[1]); 144 | continue; 145 | } 146 | const localError = getLocalErrorFromLine(line, currProjectUrl); 147 | if (localError) { 148 | localErrors.push(localError); 149 | } 150 | } 151 | // Handling the project-level errors separately makes it easier to bulk convert the file-level errors to use GH urls 152 | const errors = localErrors.filter(le => !le.path).map(le => ({ projectUrl: le.projectUrl, code: le.code, text: le.text } as Error)); 153 | 154 | const fileLocalErrors = localErrors.filter(le => le.path).map(le => ({ ...le, path: path.resolve(projectDir, le.path!) })); 155 | const fileUrls = reportGithubLinks ? await ghLink.getGithubLinks(fileLocalErrors) : fileLocalErrors.map(x => `${x.path}(${x.lineNumber},${x.columnNumber})`); 156 | for (let i = 0; i < fileLocalErrors.length; i++) { 157 | const localError = fileLocalErrors[i]; 158 | errors.push({ 159 | projectUrl: localError.projectUrl, 160 | code: localError.code, 161 | text: localError.text, 162 | fileUrl: fileUrls[i], 163 | }); 164 | } 165 | 166 | for (const error of errors) { 167 | // Drop paths to the repo (e.g., elaborations mentioning lib.d.ts files) so the messages still compare equal. 168 | // TODO: use replaceAll once that's available. 169 | error.text = error.text?.split(tsRepoPath).join(""); 170 | error.fileUrl = error.fileUrl?.split(tsRepoPath).join(""); 171 | } 172 | 173 | return { 174 | projectUrl, 175 | isComposite, 176 | hasBuildFailure, 177 | errors: errors, 178 | raw: stdout, 179 | }; 180 | } 181 | 182 | function getLocalErrorFromLine(line: string, projectUrl: string): LocalError | undefined { 183 | const errorCodeMatch = line.match(errorCodeRegex); 184 | if (errorCodeMatch) { 185 | const text = errorCodeMatch[0]; 186 | const code = +errorCodeMatch[1]; 187 | const positionMatch = line.match(errorPositionRegex); 188 | if (positionMatch) { 189 | const path = positionMatch[1]; 190 | const lineNumber = +positionMatch[2]; 191 | const columnNumber = +positionMatch[3]; 192 | 193 | return { 194 | projectUrl, 195 | code, 196 | text, 197 | path, 198 | lineNumber, 199 | columnNumber, 200 | }; 201 | } 202 | else { 203 | return { 204 | projectUrl, 205 | code, 206 | text, 207 | }; 208 | } 209 | } 210 | 211 | return undefined; 212 | } 213 | 214 | function getBuildSummary({ code, signal, stderr, stdout }: SpawnResult, mergeOutputs?: boolean) { 215 | if (mergeOutputs) { 216 | stdout = stdout + "\n" + stderr; 217 | } 218 | 219 | return { 220 | stdout, 221 | hasBuildFailure: !!(code || signal || (stderr && stderr.length && !stderr.match(/debugger/i))), // --inspect prints the debug port to stderr 222 | isEmpty: !!stdout.match(/TS18003/) 223 | }; 224 | } 225 | 226 | export class TimeoutError extends Error { 227 | constructor(message: string) { 228 | super(message); 229 | } 230 | } 231 | --------------------------------------------------------------------------------