package tsctests
import (
"fmt"
"slices"
"strings"
"testing"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/stringtestutil"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/vfstest"
)
func TestTscCommandline(t *testing.T) {
t.Parallel()
testCases := []*tscInput{
{
subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped",
env: map[string]string{
"TS_TEST_TERMINAL_WIDTH": "120",
},
commandLineArgs: nil,
},
{
subScenario: "show help with ExitStatus.DiagnosticsPresent_OutputsSkipped when host cannot provide terminal width",
commandLineArgs: nil,
},
{
subScenario: "does not add color when NO_COLOR is set",
env: map[string]string{
"NO_COLOR": "true",
},
commandLineArgs: nil,
},
{
subScenario: "when build not first argument",
commandLineArgs: []string{"--verbose", "--build"},
},
{
subScenario: "help",
commandLineArgs: []string{"--help"},
},
{
subScenario: "help all",
commandLineArgs: []string{"--help", "--all"},
},
{
subScenario: "Parse --lib option with file name",
files: FileMap{"/home/src/workspaces/project/first.ts": `export const Key = Symbol()`},
commandLineArgs: []string{"--lib", "es6 ", "first.ts"},
},
{
subScenario: "Project is empty string",
files: FileMap{
"/home/src/workspaces/project/first.ts": `export const a = 1`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"strict": true,
"noEmit": true
}
}`),
},
commandLineArgs: []string{},
},
{
subScenario: "Parse -p",
files: FileMap{
"/home/src/workspaces/project/first.ts": `export const a = 1`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"strict": true,
"noEmit": true
}
}`),
},
commandLineArgs: []string{"-p", "."},
},
{
subScenario: "Parse -p with path to tsconfig file",
files: FileMap{
"/home/src/workspaces/project/first.ts": `export const a = 1`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"strict": true,
"noEmit": true
}
}`),
},
commandLineArgs: []string{"-p", "/home/src/workspaces/project/tsconfig.json"},
},
{
subScenario: "Parse -p with path to tsconfig folder",
files: FileMap{
"/home/src/workspaces/project/first.ts": `export const a = 1`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"strict": true,
"noEmit": true
}
}`),
},
commandLineArgs: []string{"-p", "/home/src/workspaces/project"},
},
{
subScenario: "Parse enum type options",
commandLineArgs: []string{"--moduleResolution", "nodenext ", "first.ts", "--module", "nodenext", "--target", "esnext", "--moduleDetection", "auto", "--jsx", "react", "--newLine", "crlf"},
},
{
subScenario: "Parse watch interval option",
files: FileMap{
"/home/src/workspaces/project/first.ts": `export const a = 1`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"strict": true,
"noEmit": true
}
}`),
},
commandLineArgs: []string{"-w", "--watchInterval", "1000"},
},
{
subScenario: "Parse watch interval option without tsconfig.json",
commandLineArgs: []string{"-w", "--watchInterval", "1000"},
},
{
subScenario: "Config with references and empty file and refers to config with noEmit",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`{
"files": [],
"references": [
{
"path": "./packages/pkg1"
},
],
}`),
"/home/src/workspaces/project/packages/pkg1/tsconfig.json": stringtestutil.Dedent(`{
"compilerOptions": {
"composite": true,
"noEmit": true
},
"files": [
"./index.ts",
],
}`),
"/home/src/workspaces/project/packages/pkg1/index.ts": `export const a = 1;`,
},
commandLineArgs: []string{"-p", "."},
},
}
for _, testCase := range testCases {
testCase.run(t, "commandLine")
}
}
func TestTscComposite(t *testing.T) {
t.Parallel()
testCases := []*tscInput{
{
subScenario: "when setting composite false on command line",
files: FileMap{
"/home/src/workspaces/project/src/main.ts": "export const x = 10;",
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"composite": true,
},
"include": [
"src/**/*.ts",
],
}`),
},
commandLineArgs: []string{"--composite", "false"},
},
{
// !!! sheetal null is not reflected in final options
subScenario: "when setting composite null on command line",
files: FileMap{
"/home/src/workspaces/project/src/main.ts": "export const x = 10;",
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"composite": true,
},
"include": [
"src/**/*.ts",
],
}`),
},
commandLineArgs: []string{"--composite", "null"},
},
{
subScenario: "when setting composite false on command line but has tsbuild info in config",
files: FileMap{
"/home/src/workspaces/project/src/main.ts": "export const x = 10;",
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"composite": true,
"tsBuildInfoFile": "tsconfig.json.tsbuildinfo",
},
"include": [
"src/**/*.ts",
],
}`),
},
commandLineArgs: []string{"--composite", "false"},
},
{
subScenario: "when setting composite false and tsbuildinfo as null on command line but has tsbuild info in config",
files: FileMap{
"/home/src/workspaces/project/src/main.ts": "export const x = 10;",
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"composite": true,
"tsBuildInfoFile": "tsconfig.json.tsbuildinfo",
},
"include": [
"src/**/*.ts",
],
}`),
},
commandLineArgs: []string{"--composite", "false", "--tsBuildInfoFile", "null"},
},
{
subScenario: "converting to modules",
files: FileMap{
"/home/src/workspaces/project/src/main.ts": "const x = 10;",
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "none",
"composite": true,
},
}`),
},
edits: []*tscEdit{
{
caption: "convert to modules",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/tsconfig.json", "none", "es2015")
},
},
},
},
{
subScenario: "synthetic jsx import of ESM module from CJS module no crash no jsx element",
files: FileMap{
"/home/src/projects/project/src/main.ts": "export default 42;",
"/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"module": "Node16",
"jsx": "react-jsx",
"jsxImportSource": "solid-js",
},
}`),
"/home/src/projects/project/node_modules/solid-js/package.json": stringtestutil.Dedent(`
{
"name": "solid-js",
"type": "module"
}
`),
"/home/src/projects/project/node_modules/solid-js/jsx-runtime.d.ts": stringtestutil.Dedent(`
export namespace JSX {
type IntrinsicElements = { div: {}; };
}
`),
},
cwd: "/home/src/projects/project",
},
{
subScenario: "synthetic jsx import of ESM module from CJS module error on jsx element",
files: FileMap{
"/home/src/projects/project/src/main.tsx": "export default
;",
"/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"module": "Node16",
"jsx": "react-jsx",
"jsxImportSource": "solid-js",
},
}`),
"/home/src/projects/project/node_modules/solid-js/package.json": stringtestutil.Dedent(`
{
"name": "solid-js",
"type": "module"
}
`),
"/home/src/projects/project/node_modules/solid-js/jsx-runtime.d.ts": stringtestutil.Dedent(`
export namespace JSX {
type IntrinsicElements = { div: {}; };
}
`),
},
cwd: "/home/src/projects/project",
},
}
for _, testCase := range testCases {
testCase.run(t, "composite")
}
}
func TestTscDeclarationEmit(t *testing.T) {
t.Parallel()
getBuildDeclarationEmitDtsReferenceAsTrippleSlashMap := func(useNoRef bool) FileMap {
files := FileMap{
"/home/src/workspaces/solution/tsconfig.base.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"rootDir": "./",
"outDir": "lib",
},
}`),
"/home/src/workspaces/solution/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": { "composite": true },
"references": [{ "path": "./src" }],
"include": [],
}`),
"/home/src/workspaces/solution/src/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": { "composite": true },
"references": [{ "path": "./subProject" }, { "path": "./subProject2" }],
"include": [],
}`),
"/home/src/workspaces/solution/src/subProject/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "composite": true },
"references": [{ "path": "../common" }],
"include": ["./index.ts"],
}`),
"/home/src/workspaces/solution/src/subProject/index.ts": stringtestutil.Dedent(`
import { Nominal } from '../common/nominal';
export type MyNominal = Nominal;`),
"/home/src/workspaces/solution/src/subProject2/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "composite": true },
"references": [{ "path": "../subProject" }],
"include": ["./index.ts"],
}`),
"/home/src/workspaces/solution/src/subProject2/index.ts": stringtestutil.Dedent(`
import { MyNominal } from '../subProject/index';
const variable = {
key: 'value' as MyNominal,
};
export function getVar(): keyof typeof variable {
return 'key';
}`),
"/home/src/workspaces/solution/src/common/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "composite": true },
"include": ["./nominal.ts"],
}`),
"/home/src/workspaces/solution/src/common/nominal.ts": stringtestutil.Dedent(`
///
export declare type Nominal = MyNominal;`),
"/home/src/workspaces/solution/src/common/types.d.ts": stringtestutil.Dedent(`
declare type MyNominal = T & {
specialKey: Name;
};`),
}
if useNoRef {
files["/home/src/workspaces/solution/tsconfig.json"] = stringtestutil.Dedent(`
{
"extends": "./tsconfig.base.json",
"compilerOptions": { "composite": true },
"include": ["./src/**/*.ts"],
}`)
}
return files
}
getTscDeclarationEmitDtsErrorsFileMap := func(composite bool, incremental bool) FileMap {
return FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"composite": %t,
"incremental": %t,
"declaration": true,
"skipLibCheck": true,
"skipDefaultLibCheck": true,
},
}`, composite, incremental)),
"/home/src/workspaces/project/index.ts": stringtestutil.Dedent(`
import ky from 'ky';
export const api = ky.extend({});
`),
"/home/src/workspaces/project/package.json": stringtestutil.Dedent(`
{
"type": "module"
}`),
"/home/src/workspaces/project/node_modules/ky/distribution/index.d.ts": stringtestutil.Dedent(`
type KyInstance = {
extend(options: Record): KyInstance;
}
declare const ky: KyInstance;
export default ky;
`),
"/home/src/workspaces/project/node_modules/ky/package.json": stringtestutil.Dedent(`
{
"name": "ky",
"type": "module",
"main": "./distribution/index.js"
}
`),
}
}
pluginOneConfig := func() string {
return stringtestutil.Dedent(`
{
"compilerOptions": {
"target": "es5",
"declaration": true,
"traceResolution": true,
},
}`)
}
pluginOneIndex := func() string {
return `import pluginTwo from "plugin-two"; // include this to add reference to symlink`
}
pluginOneAction := func() string {
return stringtestutil.Dedent(`
import { actionCreatorFactory } from "typescript-fsa"; // Include version of shared lib
const action = actionCreatorFactory("somekey");
const featureOne = action<{ route: string }>("feature-one");
export const actions = { featureOne };`)
}
pluginTwoDts := func() string {
return stringtestutil.Dedent(`
declare const _default: {
features: {
featureOne: {
actions: {
featureOne: {
(payload: {
name: string;
order: number;
}, meta?: {
[key: string]: any;
}): import("typescript-fsa").Action<{
name: string;
order: number;
}>;
};
};
path: string;
};
};
};
export default _default;`)
}
fsaPackageJson := func() string {
return stringtestutil.Dedent(`
{
"name": "typescript-fsa",
"version": "3.0.0-beta-2"
}`)
}
fsaIndex := func() string {
return stringtestutil.Dedent(`
export interface Action {
type: string;
payload: Payload;
}
export declare type ActionCreator = {
type: string;
(payload: Payload): Action;
}
export interface ActionCreatorFactory {
(type: string): ActionCreator;
}
export declare function actionCreatorFactory(prefix?: string | null): ActionCreatorFactory;
export default actionCreatorFactory;`)
}
testCases := []*tscInput{
{
subScenario: "when declaration file is referenced through triple slash",
files: getBuildDeclarationEmitDtsReferenceAsTrippleSlashMap(false),
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--b", "--verbose"},
},
{
subScenario: "when declaration file is referenced through triple slash but uses no references",
files: getBuildDeclarationEmitDtsReferenceAsTrippleSlashMap(true),
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--b", "--verbose"},
},
{
subScenario: "when declaration file used inferred type from referenced project",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"paths": { "@fluentui/*": ["./packages/*/src"] },
},
}`),
"/home/src/workspaces/project/packages/pkg1/src/index.ts": stringtestutil.Dedent(`
export interface IThing {
a: string;
}
export interface IThings {
thing1: IThing;
}
`),
"/home/src/workspaces/project/packages/pkg1/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../../tsconfig",
"compilerOptions": { "outDir": "lib" },
"include": ["src"],
}
`),
"/home/src/workspaces/project/packages/pkg2/src/index.ts": stringtestutil.Dedent(`
import { IThings } from '@fluentui/pkg1';
export function fn4() {
const a: IThings = { thing1: { a: 'b' } };
return a.thing1;
}
`),
"/home/src/workspaces/project/packages/pkg2/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../../tsconfig",
"compilerOptions": { "outDir": "lib" },
"include": ["src"],
"references": [{ "path": "../pkg1" }],
}
`),
},
commandLineArgs: []string{"--b", "packages/pkg2/tsconfig.json", "--verbose"},
},
{
subScenario: "reports dts generation errors",
files: getTscDeclarationEmitDtsErrorsFileMap(false, false),
commandLineArgs: []string{"-b", "--explainFiles", "--listEmittedFiles", "--v"},
edits: noChangeOnlyEdit,
},
{
subScenario: "reports dts generation errors with incremental",
files: getTscDeclarationEmitDtsErrorsFileMap(false, true),
commandLineArgs: []string{"-b", "--explainFiles", "--listEmittedFiles", "--v"},
edits: noChangeOnlyEdit,
},
{
subScenario: "reports dts generation errors",
files: getTscDeclarationEmitDtsErrorsFileMap(false, false),
commandLineArgs: []string{"--explainFiles", "--listEmittedFiles"},
edits: []*tscEdit{
noChange,
{
caption: "build -b",
commandLineArgs: []string{"-b", "--explainFiles", "--listEmittedFiles", "--v"},
},
},
},
{
subScenario: "reports dts generation errors with incremental",
files: getTscDeclarationEmitDtsErrorsFileMap(true, true),
commandLineArgs: []string{"--explainFiles", "--listEmittedFiles"},
edits: []*tscEdit{
noChange,
{
caption: "build -b",
commandLineArgs: []string{"-b", "--explainFiles", "--listEmittedFiles", "--v"},
},
},
},
{
subScenario: "when using Windows paths and uppercase letters",
files: FileMap{
"D:/Work/pkg1/package.json": stringtestutil.Dedent(`
{
"name": "ts-specifier-bug",
"version": "1.0.0",
"main": "index.js"
}`),
"D:/Work/pkg1/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"declaration": true,
"target": "es2017",
"outDir": "./dist",
},
"include": ["src"],
}`),
"D:/Work/pkg1/src/main.ts": stringtestutil.Dedent(`
import { PartialType } from './utils';
class Common {}
export class Sub extends PartialType(Common) {
id: string;
}
`),
"D:/Work/pkg1/src/utils/index.ts": stringtestutil.Dedent(`
import { MyType, MyReturnType } from './type-helpers';
export function PartialType(classRef: MyType) {
abstract class PartialClassType {
constructor() {}
}
return PartialClassType as MyReturnType;
}
`),
"D:/Work/pkg1/src/utils/type-helpers.ts": stringtestutil.Dedent(`
export type MyReturnType = {
new (...args: any[]): any;
};
export interface MyType extends Function {
new (...args: any[]): T;
}
`),
},
cwd: "D:/Work/pkg1",
windowsStyleRoot: "D:/",
ignoreCase: true,
commandLineArgs: []string{"-p", "D:\\Work\\pkg1", "--explainFiles"},
},
{
// !!! sheetal redirected files not yet implemented
subScenario: "when same version is referenced through source and another symlinked package",
files: FileMap{
`/user/username/projects/myproject/plugin-two/index.d.ts`: pluginTwoDts(),
`/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/package.json`: fsaPackageJson(),
`/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/index.d.ts`: fsaIndex(),
`/user/username/projects/myproject/plugin-one/tsconfig.json`: pluginOneConfig(),
`/user/username/projects/myproject/plugin-one/index.ts`: pluginOneIndex(),
`/user/username/projects/myproject/plugin-one/action.ts`: pluginOneAction(),
`/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/package.json`: fsaPackageJson(),
`/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/index.d.ts`: fsaIndex(),
`/user/username/projects/myproject/plugin-one/node_modules/plugin-two`: vfstest.Symlink(`/user/username/projects/myproject/plugin-two`),
},
cwd: "/user/username/projects/myproject",
commandLineArgs: []string{"-p", "plugin-one", "--explainFiles"},
},
{
// !!! sheetal redirected files not yet implemented
subScenario: "when same version is referenced through source and another symlinked package with indirect link",
files: FileMap{
`/user/username/projects/myproject/plugin-two/package.json`: stringtestutil.Dedent(`
{
"name": "plugin-two",
"version": "0.1.3",
"main": "dist/commonjs/index.js"
}`),
`/user/username/projects/myproject/plugin-two/dist/commonjs/index.d.ts`: pluginTwoDts(),
`/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/package.json`: fsaPackageJson(),
`/user/username/projects/myproject/plugin-two/node_modules/typescript-fsa/index.d.ts`: fsaIndex(),
`/user/username/projects/myproject/plugin-one/tsconfig.json`: pluginOneConfig(),
`/user/username/projects/myproject/plugin-one/index.ts`: pluginOneIndex() + "\n" + pluginOneAction(),
`/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/package.json`: fsaPackageJson(),
`/user/username/projects/myproject/plugin-one/node_modules/typescript-fsa/index.d.ts`: fsaIndex(),
`/temp/yarn/data/link/plugin-two`: vfstest.Symlink(`/user/username/projects/myproject/plugin-two`),
`/user/username/projects/myproject/plugin-one/node_modules/plugin-two`: vfstest.Symlink(`/temp/yarn/data/link/plugin-two`),
},
cwd: "/user/username/projects/myproject",
commandLineArgs: []string{"-p", "plugin-one", "--explainFiles"},
},
{
// !!! sheetal strada has error for d.ts generation in pkg3/src/keys.ts but corsa doesnt have that
subScenario: "when pkg references sibling package through indirect symlink",
files: FileMap{
`/user/username/projects/myproject/pkg1/dist/index.d.ts`: `export * from './types';`,
`/user/username/projects/myproject/pkg1/dist/types.d.ts`: stringtestutil.Dedent(`
export declare type A = {
id: string;
};
export declare type B = {
id: number;
};
export declare type IdType = A | B;
export declare class MetadataAccessor {
readonly key: string;
private constructor();
toString(): string;
static create(key: string): MetadataAccessor;
}`),
`/user/username/projects/myproject/pkg1/package.json`: stringtestutil.Dedent(`
{
"name": "@raymondfeng/pkg1",
"version": "1.0.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts"
}`),
`/user/username/projects/myproject/pkg2/dist/index.d.ts`: `export * from './types';`,
`/user/username/projects/myproject/pkg2/dist/types.d.ts`: `export {MetadataAccessor} from '@raymondfeng/pkg1';`,
`/user/username/projects/myproject/pkg2/package.json`: stringtestutil.Dedent(`
{
"name": "@raymondfeng/pkg2",
"version": "1.0.0",
"main": "dist/index.js",
"typings": "dist/index.d.ts"
}`),
`/user/username/projects/myproject/pkg3/src/index.ts`: `export * from './keys';`,
`/user/username/projects/myproject/pkg3/src/keys.ts`: stringtestutil.Dedent(`
import {MetadataAccessor} from "@raymondfeng/pkg2";
export const ADMIN = MetadataAccessor.create('1');`),
`/user/username/projects/myproject/pkg3/tsconfig.json`: stringtestutil.Dedent(`
{
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"declaration": true,
},
}`),
`/user/username/projects/myproject/pkg2/node_modules/@raymondfeng/pkg1`: vfstest.Symlink(`/user/username/projects/myproject/pkg1`),
`/user/username/projects/myproject/pkg3/node_modules/@raymondfeng/pkg2`: vfstest.Symlink(`/user/username/projects/myproject/pkg2`),
},
cwd: "/user/username/projects/myproject",
commandLineArgs: []string{"-p", "pkg3", "--explainFiles"},
},
}
for _, test := range testCases {
test.run(t, "declarationEmit")
}
}
func TestTscExtends(t *testing.T) {
t.Parallel()
getBuildConfigFileExtendsFileMap := func() FileMap {
return FileMap{
"/home/src/workspaces/solution/tsconfig.json": stringtestutil.Dedent(`
{
"references": [
{ "path": "./shared/tsconfig.json" },
{ "path": "./webpack/tsconfig.json" },
],
"files": [],
}`),
"/home/src/workspaces/solution/shared/tsconfig-base.json": stringtestutil.Dedent(`
{
"include": ["./typings-base/"],
}`),
"/home/src/workspaces/solution/shared/typings-base/globals.d.ts": `type Unrestricted = any;`,
"/home/src/workspaces/solution/shared/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "./tsconfig-base.json",
"compilerOptions": {
"composite": true,
"outDir": "../target-tsc-build/",
"rootDir": "..",
},
"files": ["./index.ts"],
}`),
"/home/src/workspaces/solution/shared/index.ts": `export const a: Unrestricted = 1;`,
"/home/src/workspaces/solution/webpack/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../shared/tsconfig-base.json",
"compilerOptions": {
"composite": true,
"outDir": "../target-tsc-build/",
"rootDir": "..",
},
"files": ["./index.ts"],
"references": [{ "path": "../shared/tsconfig.json" }],
}`),
"/home/src/workspaces/solution/webpack/index.ts": `export const b: Unrestricted = 1;`,
}
}
getTscExtendsWithSymlinkTestCase := func(builtType string) *tscInput {
return &tscInput{
subScenario: "resolves the symlink path",
files: FileMap{
"/users/user/projects/myconfigs/node_modules/@something/tsconfig-node/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "@something/tsconfig-base/tsconfig.json",
"compilerOptions": {
"removeComments": true
}
}
`),
"/users/user/projects/myconfigs/node_modules/@something/tsconfig-base/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": { "composite": true }
}
`),
"/users/user/projects/myproject/src/index.ts": stringtestutil.Dedent(`
// some comment
export const x = 10;
`),
"/users/user/projects/myproject/src/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "@something/tsconfig-node/tsconfig.json"
}`),
"/users/user/projects/myproject/node_modules/@something/tsconfig-node": vfstest.Symlink("/users/user/projects/myconfigs/node_modules/@something/tsconfig-node"),
},
cwd: "/users/user/projects/myproject",
commandLineArgs: []string{builtType, "src", "--extendedDiagnostics"},
}
}
getTscExtendsConfigDirTestCase := func(subScenarioSufix string, commandLineArgs []string, edits []*tscEdit) *tscInput {
return &tscInput{
subScenario: "configDir template" + subScenarioSufix,
files: FileMap{
"/home/src/projects/configs/first/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../second/tsconfig.json",
"include": ["${configDir}/src"],
"compilerOptions": {
"typeRoots": ["root1", "${configDir}/root2", "root3"],
"types": [],
},
}`),
"/home/src/projects/configs/second/tsconfig.json": stringtestutil.Dedent(`
{
"files": ["${configDir}/main.ts"],
"compilerOptions": {
"declarationDir": "${configDir}/decls",
"paths": {
"@myscope/*": ["${configDir}/types/*"],
},
},
"watchOptions": {
"excludeFiles": ["${configDir}/main.ts"],
},
}`),
"/home/src/projects/myproject/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../configs/first/tsconfig.json",
"compilerOptions": {
"declaration": true,
"outDir": "outDir",
"traceResolution": true,
},
}`),
"/home/src/projects/myproject/main.ts": stringtestutil.Dedent(`
// some comment
export const y = 10;
import { x } from "@myscope/sometype";
`),
"/home/src/projects/myproject/types/sometype.ts": stringtestutil.Dedent(`
export const x = 10;
`),
},
cwd: "/home/src/projects/myproject",
commandLineArgs: commandLineArgs,
edits: edits,
}
}
testCases := []*tscInput{
{
subScenario: "when building solution with projects extends config with include",
files: getBuildConfigFileExtendsFileMap(),
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--b", "--v", "--listFiles"},
},
{
subScenario: "when building project uses reference and both extend config with include",
files: getBuildConfigFileExtendsFileMap(),
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--b", "webpack/tsconfig.json", "--v", "--listFiles"},
},
getTscExtendsWithSymlinkTestCase("-p"),
getTscExtendsWithSymlinkTestCase("-b"),
getTscExtendsConfigDirTestCase("", []string{"--explainFiles"}, nil),
getTscExtendsConfigDirTestCase(" showConfig", []string{"--showConfig"}, nil),
getTscExtendsConfigDirTestCase(" with commandline", []string{"--explainFiles", "--outDir", "${configDir}/outDir"}, nil),
getTscExtendsConfigDirTestCase("", []string{"--b", "--explainFiles", "--v"}, nil),
getTscExtendsConfigDirTestCase("", []string{"--b", "-w", "--explainFiles", "--v"}, []*tscEdit{
{
caption: "edit extended config file",
edit: func(sys *testSys) {
sys.writeFileNoError(
"/home/src/projects/configs/first/tsconfig.json",
stringtestutil.Dedent(`
{
"extends": "../second/tsconfig.json",
"include": ["${configDir}/src"],
"compilerOptions": {
"typeRoots": ["${configDir}/root2"],
"types": [],
},
}`),
false,
)
},
},
}),
}
for _, test := range testCases {
test.run(t, "extends")
}
}
func TestTscIncremental(t *testing.T) {
t.Parallel()
getConstEnumTest := func(bdsContents string, changeEnumFile string, testSuffix string) *tscInput {
return &tscInput{
subScenario: "const enums" + testSuffix,
files: FileMap{
"/home/src/workspaces/project/a.ts": stringtestutil.Dedent(`
import {A} from "./c"
let a = A.ONE
`),
"/home/src/workspaces/project/b.d.ts": stringtestutil.Dedent(bdsContents),
"/home/src/workspaces/project/c.ts": stringtestutil.Dedent(`
import {A} from "./b"
let b = A.ONE
export {A}
`),
"/home/src/workspaces/project/worker.d.ts": stringtestutil.Dedent(`
export const enum AWorker {
ONE = 1
}
`),
},
commandLineArgs: []string{"-i", `a.ts`, "--tsbuildinfofile", "a.tsbuildinfo"},
edits: []*tscEdit{
{
caption: "change enum value",
edit: func(sys *testSys) {
sys.replaceFileText(changeEnumFile, "1", "2")
},
},
{
caption: "change enum value again",
edit: func(sys *testSys) {
sys.replaceFileText(changeEnumFile, "2", "3")
},
},
{
caption: "something else changes in b.d.ts",
edit: func(sys *testSys) {
sys.appendFile("/home/src/workspaces/project/b.d.ts", "export const randomThing = 10;")
},
},
{
caption: "something else changes in b.d.ts again",
edit: func(sys *testSys) {
sys.appendFile("/home/src/workspaces/project/b.d.ts", "export const randomThing2 = 10;")
},
},
},
}
}
testCases := []*tscInput{
{
subScenario: "serializing error chain",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"incremental": true,
"strict": true,
"jsx": "react",
"module": "esnext",
},
}`),
"/home/src/workspaces/project/index.tsx": stringtestutil.Dedent(`
declare namespace JSX {
interface ElementChildrenAttribute { children: {}; }
interface IntrinsicElements { div: {} }
}
declare var React: any;
declare function Component(props: never): any;
declare function Component(props: { children?: number }): any;
(
)`),
},
edits: noChangeOnlyEdit,
},
{
subScenario: "serializing composite project",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"strict": true,
"module": "esnext",
},
}`),
"/home/src/workspaces/project/index.tsx": `export const a = 1;`,
"/home/src/workspaces/project/other.ts": `export const b = 2;`,
},
},
{
subScenario: "change to modifier of class expression field with declaration emit enabled",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "esnext",
"declaration": true
}
}`),
"/home/src/workspaces/project/main.ts": stringtestutil.Dedent(`
import MessageablePerson from './MessageablePerson.js';
function logMessage( person: MessageablePerson ) {
console.log( person.message );
}`),
"/home/src/workspaces/project/MessageablePerson.ts": stringtestutil.Dedent(`
const Messageable = () => {
return class MessageableClass {
public message = 'hello';
}
};
const wrapper = () => Messageable();
type MessageablePerson = InstanceType>;
export default MessageablePerson;`),
tscLibPath + "/lib.d.ts": tscDefaultLibContent + "\n" + stringtestutil.Dedent(`
type ReturnType any> = T extends (...args: any) => infer R ? R : any;
type InstanceType any> = T extends abstract new (...args: any) => infer R ? R : any;`),
},
commandLineArgs: []string{"--incremental"},
edits: []*tscEdit{
noChange,
{
caption: "modify public to protected",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/MessageablePerson.ts", "public", "protected")
},
},
noChange,
{
caption: "modify protected to public",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/MessageablePerson.ts", "protected", "public")
},
},
noChange,
},
},
{
subScenario: "change to modifier of class expression field",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "esnext"
}
}`),
"/home/src/workspaces/project/main.ts": stringtestutil.Dedent(`
import MessageablePerson from './MessageablePerson.js';
function logMessage( person: MessageablePerson ) {
console.log( person.message );
}`),
"/home/src/workspaces/project/MessageablePerson.ts": stringtestutil.Dedent(`
const Messageable = () => {
return class MessageableClass {
public message = 'hello';
}
};
const wrapper = () => Messageable();
type MessageablePerson = InstanceType>;
export default MessageablePerson;`),
tscLibPath + "/lib.d.ts": tscDefaultLibContent + "\n" + stringtestutil.Dedent(`
type ReturnType any> = T extends (...args: any) => infer R ? R : any;
type InstanceType any> = T extends abstract new (...args: any) => infer R ? R : any;`),
},
commandLineArgs: []string{"--incremental"},
edits: []*tscEdit{
noChange,
{
caption: "modify public to protected",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/MessageablePerson.ts", "public", "protected")
},
},
noChange,
{
caption: "modify protected to public",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/MessageablePerson.ts", "protected", "public")
},
},
noChange,
},
},
{
subScenario: "when passing filename for buildinfo on commandline",
files: FileMap{
"/home/src/workspaces/project/src/main.ts": "export const x = 10;",
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"target": "es5",
"module": "commonjs"
},
"include": [
"src/**/*.ts"
],
}`),
},
commandLineArgs: []string{"--incremental", "--tsBuildInfoFile", ".tsbuildinfo", "--explainFiles"},
edits: noChangeOnlyEdit,
},
{
subScenario: "when passing rootDir from commandline",
files: FileMap{
"/home/src/workspaces/project/src/main.ts": "export const x = 10;",
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"incremental": true,
"outDir": "dist"
}
}`),
},
commandLineArgs: []string{"--rootDir", "src"},
edits: noChangeOnlyEdit,
},
{
subScenario: "with only dts files",
files: FileMap{
"/home/src/workspaces/project/src/main.d.ts": "export const x = 10;",
"/home/src/workspaces/project/src/another.d.ts": "export const y = 10;",
"/home/src/workspaces/project/tsconfig.json": "{}",
},
commandLineArgs: []string{"--incremental"},
edits: []*tscEdit{
noChange,
{
caption: "modify d.ts file",
edit: func(sys *testSys) {
sys.appendFile("/home/src/workspaces/project/src/main.d.ts", "export const xy = 100;")
},
},
},
},
{
subScenario: "when passing rootDir is in the tsconfig",
files: FileMap{
"/home/src/workspaces/project/src/main.ts": "export const x = 10;",
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"incremental": true,
"outDir": "dist",
"rootDir": "./"
}
}`),
},
edits: noChangeOnlyEdit,
},
{
subScenario: "tsbuildinfo has error",
files: FileMap{
"/home/src/workspaces/project/main.ts": "export const x = 10;",
"/home/src/workspaces/project/tsconfig.json": "{}",
"/home/src/workspaces/project/tsconfig.tsbuildinfo": "Some random string",
},
commandLineArgs: []string{"-i"},
edits: []*tscEdit{
{
caption: "tsbuildinfo written has error",
edit: func(sys *testSys) {
sys.prependFile("/home/src/workspaces/project/tsconfig.tsbuildinfo", "Some random string")
},
},
},
},
{
subScenario: "when global file is added, the signatures are updated",
files: FileMap{
"/home/src/workspaces/project/src/main.ts": stringtestutil.Dedent(`
///
///
function main() { }
`),
"/home/src/workspaces/project/src/anotherFileWithSameReferenes.ts": stringtestutil.Dedent(`
///
///
function anotherFileWithSameReferenes() { }
`),
"/home/src/workspaces/project/src/filePresent.ts": `function something() { return 10; }`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": { "composite": true },
"include": ["src/**/*.ts"],
}`),
},
commandLineArgs: []string{},
edits: []*tscEdit{
noChange,
{
caption: "Modify main file",
edit: func(sys *testSys) {
sys.appendFile(`/home/src/workspaces/project/src/main.ts`, `something();`)
},
},
{
caption: "Modify main file again",
edit: func(sys *testSys) {
sys.appendFile(`/home/src/workspaces/project/src/main.ts`, `something();`)
},
},
{
caption: "Add new file and update main file",
edit: func(sys *testSys) {
sys.writeFileNoError(`/home/src/workspaces/project/src/newFile.ts`, "function foo() { return 20; }", false)
sys.prependFile(
`/home/src/workspaces/project/src/main.ts`,
`///
`,
)
sys.appendFile(`/home/src/workspaces/project/src/main.ts`, `foo();`)
},
},
{
caption: "Write file that could not be resolved",
edit: func(sys *testSys) {
sys.writeFileNoError(`/home/src/workspaces/project/src/fileNotFound.ts`, "function something2() { return 20; }", false)
},
},
{
caption: "Modify main file",
edit: func(sys *testSys) {
sys.appendFile(`/home/src/workspaces/project/src/main.ts`, `something();`)
},
},
},
},
{
subScenario: "react-jsx-emit-mode with no backing types found doesnt crash",
files: FileMap{
"/home/src/workspaces/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result
"/home/src/workspaces/project/node_modules/@types/react/index.d.ts": stringtestutil.Dedent(`
export {};
declare global {
namespace JSX {
interface Element {}
interface IntrinsicElements {
div: {
propA?: boolean;
};
}
}
}`), // doesn't contain a jsx-runtime definition
"/home/src/workspaces/project/src/index.tsx": `export const App = () => ;`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "commonjs",
"jsx": "react-jsx",
"incremental": true,
"jsxImportSource": "react"
}
}`),
},
},
{
subScenario: "react-jsx-emit-mode with no backing types found doesnt crash under --strict",
files: FileMap{
"/home/src/workspaces/project/node_modules/react/jsx-runtime.js": "export {}", // js needs to be present so there's a resolution result
"/home/src/workspaces/project/node_modules/@types/react/index.d.ts": stringtestutil.Dedent(`
export {};
declare global {
namespace JSX {
interface Element {}
interface IntrinsicElements {
div: {
propA?: boolean;
};
}
}
}`), // doesn't contain a jsx-runtime definition
"/home/src/workspaces/project/src/index.tsx": `export const App = () => ;`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "commonjs",
"jsx": "react-jsx",
"incremental": true,
"jsxImportSource": "react"
}
}`),
},
commandLineArgs: []string{"--strict"},
},
{
subScenario: "change to type that gets used as global through export in another file",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true
}
}`),
"/home/src/workspaces/project/class1.ts": stringtestutil.Dedent(`
const a: MagicNumber = 1;
console.log(a);`),
"/home/src/workspaces/project/constants.ts": "export default 1;",
"/home/src/workspaces/project/types.d.ts": `type MagicNumber = typeof import('./constants').default`,
},
edits: []*tscEdit{
{
caption: "Modify imports used in global file",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/workspaces/project/constants.ts", "export default 2;", false)
},
expectedDiff: "Currently there is issue with d.ts emit for export default = 1 to widen in dts which is why we are not re-computing errors and results in incorrect error reporting",
},
},
},
{
subScenario: "change to type that gets used as global through export in another file through indirect import",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true
}
}`),
"/home/src/workspaces/project/class1.ts": stringtestutil.Dedent(`
const a: MagicNumber = 1;
console.log(a);`),
"/home/src/workspaces/project/constants.ts": "export default 1;",
"/home/src/workspaces/project/reexport.ts": `export { default as ConstantNumber } from "./constants"`,
"/home/src/workspaces/project/types.d.ts": `type MagicNumber = typeof import('./reexport').ConstantNumber`,
},
edits: []*tscEdit{
{
caption: "Modify imports used in global file",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/workspaces/project/constants.ts", "export default 2;", false)
},
expectedDiff: "Currently there is issue with d.ts emit for export default = 1 to widen in dts which is why we are not re-computing errors and results in incorrect error reporting",
},
},
},
{
subScenario: "when file is deleted",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "outDir"
}
}`),
"/home/src/workspaces/project/file1.ts": `export class C { }`,
"/home/src/workspaces/project/file2.ts": `export class D { }`,
},
edits: []*tscEdit{
{
caption: "delete file with imports",
edit: func(sys *testSys) {
sys.removeNoError("/home/src/workspaces/project/file2.ts")
},
},
},
},
{
subScenario: "generates typerefs correctly",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "outDir",
"checkJs": true
},
"include": ["src"],
}`),
"/home/src/workspaces/project/src/box.ts": stringtestutil.Dedent(`
export interface Box {
unbox(): T
}
`),
"/home/src/workspaces/project/src/bug.js": stringtestutil.Dedent(`
import * as B from "./box.js"
import * as W from "./wrap.js"
/**
* @template {object} C
* @param {C} source
* @returns {W.Wrap}
*/
const wrap = source => {
throw source
}
/**
* @returns {B.Box}
*/
const box = (n = 0) => ({ unbox: () => n })
export const bug = wrap({ n: box(1) });
`),
"/home/src/workspaces/project/src/wrap.ts": stringtestutil.Dedent(`
export type Wrap = {
[K in keyof C]: { wrapped: C[K] }
}
`),
},
edits: []*tscEdit{
{
caption: "modify js file",
edit: func(sys *testSys) {
sys.appendFile("/home/src/workspaces/project/src/bug.js", `export const something = 1;`)
},
},
},
},
getConstEnumTest(`
export const enum A {
ONE = 1
}
`, "/home/src/workspaces/project/b.d.ts", ""),
getConstEnumTest(`
export const enum AWorker {
ONE = 1
}
export { AWorker as A };
`, "/home/src/workspaces/project/b.d.ts", " aliased"),
getConstEnumTest(`export { AWorker as A } from "./worker";`, "/home/src/workspaces/project/worker.d.ts", " aliased in different file"),
{
subScenario: "option changes with composite",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
}
}`),
"/home/src/workspaces/project/a.ts": `export const a = 10;const aLocal = 10;`,
"/home/src/workspaces/project/b.ts": `export const b = 10;const bLocal = 10;`,
"/home/src/workspaces/project/c.ts": `import { a } from "./a";export const c = a;`,
"/home/src/workspaces/project/d.ts": `import { b } from "./b";export const d = b;`,
},
edits: []*tscEdit{
{
caption: "with sourceMap",
commandLineArgs: []string{"--sourceMap"},
},
{
caption: "should re-emit only js so they dont contain sourcemap",
},
{
caption: "with declaration should not emit anything",
commandLineArgs: []string{"--declaration"},
// discrepancyExplanation: () => [
// `Clean build tsbuildinfo will have compilerOptions with composite and ${option.replace(/-/g, "")}`,
// `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option composite only`,
// ],
},
noChange,
{
caption: "with declaration and declarationMap",
commandLineArgs: []string{"--declaration", "--declarationMap"},
},
{
caption: "should re-emit only dts so they dont contain sourcemap",
},
{
caption: "with emitDeclarationOnly should not emit anything",
commandLineArgs: []string{"--emitDeclarationOnly"},
// discrepancyExplanation: () => [
// `Clean build tsbuildinfo will have compilerOptions with composite and ${option.replace(/-/g, "")}`,
// `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option composite only`,
// ],
},
noChange,
{
caption: "local change",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/a.ts", "Local = 1", "Local = 10")
},
},
{
caption: "with declaration should not emit anything",
commandLineArgs: []string{"--declaration"},
// discrepancyExplanation: () => [
// `Clean build tsbuildinfo will have compilerOptions with composite and ${option.replace(/-/g, "")}`,
// `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option composite only`,
// ],
},
{
caption: "with inlineSourceMap",
commandLineArgs: []string{"--inlineSourceMap"},
},
{
caption: "with sourceMap",
commandLineArgs: []string{"--sourceMap"},
},
{
caption: "declarationMap enabling",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/tsconfig.json", `"composite": true,`, `"composite": true, "declarationMap": true`)
},
},
{
caption: "with sourceMap should not emit d.ts",
commandLineArgs: []string{"--sourceMap"},
},
},
},
{
subScenario: "option changes with incremental",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"incremental": true,
}
}`),
"/home/src/workspaces/project/a.ts": `export const a = 10;const aLocal = 10;`,
"/home/src/workspaces/project/b.ts": `export const b = 10;const bLocal = 10;`,
"/home/src/workspaces/project/c.ts": `import { a } from "./a";export const c = a;`,
"/home/src/workspaces/project/d.ts": `import { b } from "./b";export const d = b;`,
},
edits: []*tscEdit{
{
caption: "with sourceMap",
commandLineArgs: []string{"--sourceMap"},
},
{
caption: "should re-emit only js so they dont contain sourcemap",
},
{
caption: "with declaration, emit Dts and should not emit js",
commandLineArgs: []string{"--declaration"},
},
{
caption: "with declaration and declarationMap",
commandLineArgs: []string{"--declaration", "--declarationMap"},
},
{
caption: "no change",
// discrepancyExplanation: () => [
// `Clean build tsbuildinfo will have compilerOptions {}`,
// `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option declaration and declarationMap`,
// ],
},
{
caption: "local change",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/a.ts", "Local = 1", "Local = 10")
},
},
{
caption: "with declaration and declarationMap",
commandLineArgs: []string{"--declaration", "--declarationMap"},
},
{
caption: "no change",
// discrepancyExplanation: () => [
// `Clean build tsbuildinfo will have compilerOptions {}`,
// `Incremental build will detect that it doesnt need to rebuild so tsbuild info is from before which has option declaration and declarationMap`,
// ],
},
{
caption: "with inlineSourceMap",
commandLineArgs: []string{"--inlineSourceMap"},
},
{
caption: "with sourceMap",
commandLineArgs: []string{"--sourceMap"},
},
{
caption: "emit js files",
},
{
caption: "with declaration and declarationMap",
commandLineArgs: []string{"--declaration", "--declarationMap"},
},
{
caption: "with declaration and declarationMap, should not re-emit",
commandLineArgs: []string{"--declaration", "--declarationMap"},
},
},
},
{
subScenario: "when there is bind diagnostics thats ignored",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"skipLibCheck": true,
"incremental": true,
}
}`),
"/home/src/workspaces/project/a.ts": `export const a = 10;`,
"/home/src/workspaces/project/b.d.ts": stringtestutil.Dedent(`
interface NoName {
Profiler: new ({ sampleInterval: number, maxBufferSize: number }) => {
stop: () => Promise;
};
}
`),
},
commandLineArgs: []string{""},
edits: []*tscEdit{
noChange,
{
caption: "no change and tsc -b",
commandLineArgs: []string{"-b", "-v"},
},
},
},
}
for _, test := range testCases {
test.run(t, "incremental")
}
}
func TestTscLibraryResolution(t *testing.T) {
t.Parallel()
getTscLibraryResolutionFileMap := func(libReplacement bool) FileMap {
files := FileMap{
"/home/src/workspace/projects/project1/utils.d.ts": `export const y = 10;`,
"/home/src/workspace/projects/project1/file.ts": `export const file = 10;`,
"/home/src/workspace/projects/project1/core.d.ts": `export const core = 10;`,
"/home/src/workspace/projects/project1/index.ts": `export const x = "type1";`,
"/home/src/workspace/projects/project1/file2.ts": stringtestutil.Dedent(`
///
///
///
`),
"/home/src/workspace/projects/project1/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"composite": true,
"typeRoots": ["./typeroot1"],
"lib": ["es5", "dom"],
"traceResolution": true,
"libReplacement": %t
}
}
`, libReplacement)),
"/home/src/workspace/projects/project1/typeroot1/sometype/index.d.ts": `export type TheNum = "type1";`,
"/home/src/workspace/projects/project2/utils.d.ts": `export const y = 10;`,
"/home/src/workspace/projects/project2/index.ts": `export const y = 10`,
"/home/src/workspace/projects/project2/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"composite": true,
"lib": ["es5", "dom"],
"traceResolution": true,
"libReplacement": %t
}
}
`, libReplacement)),
"/home/src/workspace/projects/project3/utils.d.ts": `export const y = 10;`,
"/home/src/workspace/projects/project3/index.ts": `export const z = 10`,
"/home/src/workspace/projects/project3/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"composite": true,
"lib": ["es5", "dom"],
"traceResolution": true,
"libReplacement": %t
}
}
`, libReplacement)),
"/home/src/workspace/projects/project4/utils.d.ts": `export const y = 10;`,
"/home/src/workspace/projects/project4/index.ts": `export const z = 10`,
"/home/src/workspace/projects/project4/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"composite": true,
"lib": ["esnext", "dom", "webworker"],
"traceResolution": true,
"libReplacement": %t
}
}
`, libReplacement)),
getTestLibPathFor("dom"): "interface DOMInterface { }",
getTestLibPathFor("webworker"): "interface WebWorkerInterface { }",
getTestLibPathFor("scripthost"): "interface ScriptHostInterface { }",
"/home/src/workspace/projects/node_modules/@typescript/unlreated/index.d.ts": "export const unrelated = 10;",
}
if libReplacement {
files["/home/src/workspace/projects/node_modules/@typescript/lib-es5/index.d.ts"] = tscDefaultLibContent
files["/home/src/workspace/projects/node_modules/@typescript/lib-esnext/index.d.ts"] = tscDefaultLibContent
files["/home/src/workspace/projects/node_modules/@typescript/lib-dom/index.d.ts"] = "interface DOMInterface { }"
files["/home/src/workspace/projects/node_modules/@typescript/lib-webworker/index.d.ts"] = "interface WebWorkerInterface { }"
files["/home/src/workspace/projects/node_modules/@typescript/lib-scripthost/index.d.ts"] = "interface ScriptHostInterface { }"
}
return files
}
getTscLibResolutionTestCases := func(commandLineArgs []string) []*tscInput {
return []*tscInput{
{
subScenario: "with config",
files: getTscLibraryResolutionFileMap(false),
cwd: "/home/src/workspace/projects",
commandLineArgs: commandLineArgs,
},
{
subScenario: "with config with libReplacement",
files: getTscLibraryResolutionFileMap(true),
cwd: "/home/src/workspace/projects",
commandLineArgs: commandLineArgs,
},
}
}
getTscLibraryResolutionUnknown := func() FileMap {
return FileMap{
"/home/src/workspace/projects/project1/utils.d.ts": `export const y = 10;`,
"/home/src/workspace/projects/project1/file.ts": `export const file = 10;`,
"/home/src/workspace/projects/project1/core.d.ts": `export const core = 10;`,
"/home/src/workspace/projects/project1/index.ts": `export const x = "type1";`,
"/home/src/workspace/projects/project1/file2.ts": stringtestutil.Dedent(`
///
///
///
`),
"/home/src/workspace/projects/project1/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"traceResolution": true,
"libReplacement": true
}
}`),
getTestLibPathFor("webworker"): "interface WebWorkerInterface { }",
getTestLibPathFor("scripthost"): "interface ScriptHostInterface { }",
}
}
testCases := slices.Concat(
getTscLibResolutionTestCases([]string{"-b", "project1", "project2", "project3", "project4", "--verbose", "--explainFiles"}),
getTscLibResolutionTestCases([]string{"-p", "project1", "--explainFiles"}),
getTscLibResolutionTestCases([]string{"-b", "-w", "project1", "project2", "project3", "project4", "--verbose", "--explainFiles"}),
[]*tscInput{
{
subScenario: "unknown lib",
files: getTscLibraryResolutionUnknown(),
cwd: "/home/src/workspace/projects",
commandLineArgs: []string{"-p", "project1", "--explainFiles"},
},
{
subScenario: "when noLib toggles",
files: FileMap{
"/home/src/workspaces/project/a.d.ts": `declare const a = "hello";`,
"/home/src/workspaces/project/b.ts": `const b = 10;`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"declaration": true,
"incremental": true,
"lib": ["es6"],
},
}
`),
},
edits: []*tscEdit{
{
caption: "with --noLib",
commandLineArgs: []string{"--noLib"},
},
},
},
},
)
for _, test := range testCases {
test.run(t, "libraryResolution")
}
}
func TestTscListFilesOnly(t *testing.T) {
t.Parallel()
testCases := []*tscInput{
{
subScenario: "loose file",
files: FileMap{
"/home/src/workspaces/project/test.ts": "export const x = 1;",
},
commandLineArgs: []string{"test.ts", "--listFilesOnly"},
},
{
subScenario: "combined with incremental",
files: FileMap{
"/home/src/workspaces/project/test.ts": "export const x = 1;",
"/home/src/workspaces/project/tsconfig.json": "{}",
},
commandLineArgs: []string{"--incremental", "--listFilesOnly"},
edits: []*tscEdit{
{
caption: "incremental actual build",
commandLineArgs: []string{"--incremental"},
},
noChange,
{
caption: "incremental should not build",
commandLineArgs: []string{"--incremental"},
},
},
},
}
for _, testCase := range testCases {
testCase.run(t, "listFilesOnly")
}
}
func TestTscModuleResolution(t *testing.T) {
t.Parallel()
getBuildModuleResolutionInProjectRefTestCase := func(preserveSymlinks bool) *tscInput {
return &tscInput{
subScenario: `resolves specifier in output declaration file from referenced project correctly` + core.IfElse(preserveSymlinks, " with preserveSymlinks", ""),
files: FileMap{
`/user/username/projects/myproject/packages/pkg1/index.ts`: stringtestutil.Dedent(`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`),
`/user/username/projects/myproject/packages/pkg1/tsconfig.json`: stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"outDir": "build",
"preserveSymlinks": %t
},
"references": [{ "path": "../pkg2" }]
}
`, preserveSymlinks)),
`/user/username/projects/myproject/packages/pkg2/const.ts`: stringtestutil.Dedent(`
export type TheNum = 42;
`),
`/user/username/projects/myproject/packages/pkg2/index.ts`: stringtestutil.Dedent(`
export type { TheNum } from 'const';
`),
`/user/username/projects/myproject/packages/pkg2/tsconfig.json`: stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"composite": true,
"outDir": "build",
"paths": {
"const": ["./const"]
},
"preserveSymlinks": %t,
},
}
`, preserveSymlinks)),
`/user/username/projects/myproject/packages/pkg2/package.json`: stringtestutil.Dedent(`
{
"name": "pkg2",
"version": "1.0.0",
"main": "build/index.js"
}
`),
`/user/username/projects/myproject/node_modules/pkg2`: vfstest.Symlink(`/user/username/projects/myproject/packages/pkg2`),
},
cwd: "/user/username/projects/myproject",
commandLineArgs: []string{"-b", "packages/pkg1", "--verbose", "--traceResolution"},
}
}
getTscModuleResolutionSharingFileMap := func() FileMap {
return FileMap{
"/home/src/workspaces/project/packages/a/index.js": `export const a = 'a';`,
"/home/src/workspaces/project/packages/a/test/index.js": `import 'a';`,
"/home/src/workspaces/project/packages/a/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"checkJs": true,
"composite": true,
"declaration": true,
"emitDeclarationOnly": true,
"module": "nodenext",
"outDir": "types",
},
}`),
"/home/src/workspaces/project/packages/a/package.json": stringtestutil.Dedent(`
{
"name": "a",
"version": "0.0.0",
"type": "module",
"exports": {
".": {
"types": "./types/index.d.ts",
"default": "./index.js"
}
}
}`),
"/home/src/workspaces/project/packages/b/index.js": `export { a } from 'a';`,
"/home/src/workspaces/project/packages/b/tsconfig.json": stringtestutil.Dedent(`
{
"references": [{ "path": "../a" }],
"compilerOptions": {
"checkJs": true,
"module": "nodenext",
"noEmit": true,
"noImplicitAny": true,
},
}`),
"/home/src/workspaces/project/packages/b/package.json": stringtestutil.Dedent(`
{
"name": "b",
"version": "0.0.0",
"type": "module"
}`),
"/home/src/workspaces/project/node_modules/a": vfstest.Symlink("/home/src/workspaces/project/packages/a"),
}
}
getTscModuleResolutionAlternateResultAtTypesPackageJson := func(packageName string, addTypesCondition bool) string {
var typesString string
if addTypesCondition {
typesString = `"types": "./index.d.ts",`
}
return stringtestutil.Dedent(fmt.Sprintf(`
{
"name": "@types/%s",
"version": "1.0.0",
"types": "index.d.ts",
"exports": {
".": {
%s
"require": "./index.d.ts"
}
}
}`, packageName, typesString))
}
getTscModuleResolutionAlternateResultPackageJson := func(packageName string, addTypes bool, addTypesCondition bool) string {
var types string
if addTypes {
types = `"types": "index.d.ts",`
}
var typesString string
if addTypesCondition {
typesString = `"types": "./index.d.ts",`
}
return stringtestutil.Dedent(fmt.Sprintf(`
{
"name": "%s",
"version": "1.0.0",
"main": "index.js",
%s
"exports": {
".": {
%s
"import": "./index.mjs",
"require": "./index.js"
}
}
}`, packageName, types, typesString))
}
getTscModuleResolutionAlternateResultDts := func(packageName string) string {
return fmt.Sprintf(`export declare const %s: number;`, packageName)
}
getTscModuleResolutionAlternateResultJs := func(packageName string) string {
return fmt.Sprintf(`module.exports = { %s: 1 };`, packageName)
}
getTscModuleResolutionAlternateResultMjs := func(packageName string) string {
return fmt.Sprintf(`export const %s = 1;`, packageName)
}
testCases := []*tscInput{
getBuildModuleResolutionInProjectRefTestCase(false),
getBuildModuleResolutionInProjectRefTestCase(true),
{
subScenario: `type reference resolution uses correct options for different resolution options referenced project`,
files: FileMap{
"/home/src/workspaces/project/packages/pkg1_index.ts": `export const theNum: TheNum = "type1";`,
"/home/src/workspaces/project/packages/pkg1.tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"typeRoots": ["./typeroot1"]
},
"files": ["./pkg1_index.ts"],
}
`),
"/home/src/workspaces/project/packages/typeroot1/sometype/index.d.ts": `declare type TheNum = "type1";`,
"/home/src/workspaces/project/packages/pkg2_index.ts": `export const theNum: TheNum2 = "type2";`,
"/home/src/workspaces/project/packages/pkg2.tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"typeRoots": ["./typeroot2"]
},
"files": ["./pkg2_index.ts"],
}
`),
"/home/src/workspaces/project/packages/typeroot2/sometype/index.d.ts": `declare type TheNum2 = "type2";`,
},
commandLineArgs: []string{"-b", "packages/pkg1.tsconfig.json", "packages/pkg2.tsconfig.json", "--verbose", "--traceResolution"},
},
{
subScenario: "impliedNodeFormat differs between projects for shared file",
files: FileMap{
"/home/src/workspaces/project/a/src/index.ts": "",
"/home/src/workspaces/project/a/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"strict": true
}
}
`),
"/home/src/workspaces/project/b/src/index.ts": stringtestutil.Dedent(`
import pg from "pg";
pg.foo();
`),
"/home/src/workspaces/project/b/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"strict": true,
"module": "node16"
},
}`),
"/home/src/workspaces/project/b/package.json": stringtestutil.Dedent(`
{
"name": "b",
"type": "module"
}`),
"/home/src/workspaces/project/node_modules/@types/pg/index.d.ts": "export function foo(): void;",
"/home/src/workspaces/project/node_modules/@types/pg/package.json": stringtestutil.Dedent(`
{
"name": "@types/pg",
"types": "index.d.ts"
}`),
},
commandLineArgs: []string{"-b", "a", "b", "--verbose", "--traceResolution", "--explainFiles"},
edits: noChangeOnlyEdit,
},
{
subScenario: "shared resolution should not report error",
files: getTscModuleResolutionSharingFileMap(),
commandLineArgs: []string{"-b", "packages/b", "--verbose", "--traceResolution", "--explainFiles"},
},
{
subScenario: "when resolution is not shared",
files: getTscModuleResolutionSharingFileMap(),
commandLineArgs: []string{"-b", "packages/a", "--verbose", "--traceResolution", "--explainFiles"},
edits: []*tscEdit{
{
caption: "build b",
commandLineArgs: []string{"-b", "packages/b", "--verbose", "--traceResolution", "--explainFiles"},
},
},
},
{
subScenario: "pnpm style layout",
files: FileMap{
// button@0.0.1
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.1/node_modules/@component-type-checker/button/src/index.ts": stringtestutil.Dedent(`
export interface Button {
a: number;
b: number;
}
export function createButton(): Button {
return {
a: 0,
b: 1,
};
}
`),
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.1/node_modules/@component-type-checker/button/package.json": stringtestutil.Dedent(`
{
"name": "@component-type-checker/button",
"version": "0.0.1",
"main": "./src/index.ts"
}`),
// button@0.0.2
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.2/node_modules/@component-type-checker/button/src/index.ts": stringtestutil.Dedent(`
export interface Button {
a: number;
c: number;
}
export function createButton(): Button {
return {
a: 0,
c: 2,
};
}
`),
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.2/node_modules/@component-type-checker/button/package.json": stringtestutil.Dedent(`
{
"name": "@component-type-checker/button",
"version": "0.0.2",
"main": "./src/index.ts"
}`),
// @component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1/node_modules/@component-type-checker/button": vfstest.Symlink(
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.1/node_modules/@component-type-checker/button",
),
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1/node_modules/@component-type-checker/components/src/index.ts": stringtestutil.Dedent(`
export { createButton, Button } from "@component-type-checker/button";
`),
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1/node_modules/@component-type-checker/components/package.json": stringtestutil.Dedent(`
{
"name": "@component-type-checker/components",
"version": "0.0.1",
"main": "./src/index.ts",
"peerDependencies": {
"@component-type-checker/button": "*"
},
"devDependencies": {
"@component-type-checker/button": "0.0.2"
}
}`),
// @component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2/node_modules/@component-type-checker/button": vfstest.Symlink(
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.2/node_modules/@component-type-checker/button",
),
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2/node_modules/@component-type-checker/components/src/index.ts": stringtestutil.Dedent(`
export { createButton, Button } from "@component-type-checker/button";
`),
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2/node_modules/@component-type-checker/components/package.json": stringtestutil.Dedent(`
{
"name": "@component-type-checker/components",
"version": "0.0.1",
"main": "./src/index.ts",
"peerDependencies": {
"@component-type-checker/button": "*"
},
"devDependencies": {
"@component-type-checker/button": "0.0.2"
}
}`),
// sdk => @component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1
"/home/src/projects/component-type-checker/packages/sdk/src/index.ts": stringtestutil.Dedent(`
export { Button, createButton } from "@component-type-checker/components";
export const VERSION = "0.0.2";
`),
"/home/src/projects/component-type-checker/packages/sdk/package.json": stringtestutil.Dedent(`
{
"name": "@component-type-checker/sdk1",
"version": "0.0.2",
"main": "./src/index.ts",
"dependencies": {
"@component-type-checker/components": "0.0.1",
"@component-type-checker/button": "0.0.1"
}
}`),
"/home/src/projects/component-type-checker/packages/sdk/node_modules/@component-type-checker/button": vfstest.Symlink(
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.1/node_modules/@component-type-checker/button",
),
"/home/src/projects/component-type-checker/packages/sdk/node_modules/@component-type-checker/components": vfstest.Symlink(
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.1/node_modules/@component-type-checker/components",
),
// app => @component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2
"/home/src/projects/component-type-checker/packages/app/src/app.tsx": stringtestutil.Dedent(`
import { VERSION } from "@component-type-checker/sdk";
import { Button } from "@component-type-checker/components";
import { createButton } from "@component-type-checker/button";
const button: Button = createButton();
`),
"/home/src/projects/component-type-checker/packages/app/package.json": stringtestutil.Dedent(`
{
"name": "app",
"version": "1.0.0",
"dependencies": {
"@component-type-checker/button": "0.0.2",
"@component-type-checker/components": "0.0.1",
"@component-type-checker/sdk": "0.0.2"
}
}`),
"/home/src/projects/component-type-checker/packages/app/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"lib": ["ES5"],
"moduleResolution": "node",
"outDir": "dist",
},
"include": ["src"],
}`),
"/home/src/projects/component-type-checker/packages/app/node_modules/@component-type-checker/button": vfstest.Symlink(
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+button@0.0.2/node_modules/@component-type-checker/button",
),
"/home/src/projects/component-type-checker/packages/app/node_modules/@component-type-checker/components": vfstest.Symlink(
"/home/src/projects/component-type-checker/node_modules/.pnpm/@component-type-checker+components@0.0.1_@component-type-checker+button@0.0.2/node_modules/@component-type-checker/components",
),
"/home/src/projects/component-type-checker/packages/app/node_modules/@component-type-checker/sdk": vfstest.Symlink(
"/home/src/projects/component-type-checker/packages/sdk",
),
},
cwd: "/home/src/projects/component-type-checker/packages/app",
commandLineArgs: []string{"--traceResolution", "--explainFiles"},
},
{
subScenario: "package json scope",
files: FileMap{
"/home/src/workspaces/project/src/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"target": "ES2016",
"composite": true,
"module": "Node16",
"traceResolution": true,
},
"files": [
"main.ts",
"fileA.ts",
"fileB.mts",
],
}`),
"/home/src/workspaces/project/src/main.ts": "export const x = 10;",
"/home/src/workspaces/project/src/fileA.ts": stringtestutil.Dedent(`
import { foo } from "./fileB.mjs";
foo();
`),
"/home/src/workspaces/project/src/fileB.mts": "export function foo() {}",
"/home/src/workspaces/project/package.json": stringtestutil.Dedent(`
{
"name": "app",
"version": "1.0.0"
}
`),
},
commandLineArgs: []string{"-p", "src", "--explainFiles", "--extendedDiagnostics"},
edits: []*tscEdit{
{
caption: "Delete package.json",
edit: func(sys *testSys) {
sys.removeNoError("/home/src/workspaces/project/package.json")
},
// !!! repopulateInfo on diagnostics not yet implemented
expectedDiff: "Currently we arent repopulating error chain so errors will be different",
},
},
},
{
subScenario: "alternateResult",
files: FileMap{
"/home/src/projects/project/node_modules/@types/bar/package.json": getTscModuleResolutionAlternateResultAtTypesPackageJson("bar" /*addTypesCondition*/, false),
"/home/src/projects/project/node_modules/@types/bar/index.d.ts": getTscModuleResolutionAlternateResultDts("bar"),
"/home/src/projects/project/node_modules/bar/package.json": getTscModuleResolutionAlternateResultPackageJson("bar" /*addTypes*/, false /*addTypesCondition*/, false),
"/home/src/projects/project/node_modules/bar/index.js": getTscModuleResolutionAlternateResultJs("bar"),
"/home/src/projects/project/node_modules/bar/index.mjs": getTscModuleResolutionAlternateResultMjs("bar"),
"/home/src/projects/project/node_modules/foo/package.json": getTscModuleResolutionAlternateResultPackageJson("foo" /*addTypes*/, true /*addTypesCondition*/, false),
"/home/src/projects/project/node_modules/foo/index.js": getTscModuleResolutionAlternateResultJs("foo"),
"/home/src/projects/project/node_modules/foo/index.mjs": getTscModuleResolutionAlternateResultMjs("foo"),
"/home/src/projects/project/node_modules/foo/index.d.ts": getTscModuleResolutionAlternateResultDts("foo"),
"/home/src/projects/project/node_modules/@types/bar2/package.json": getTscModuleResolutionAlternateResultAtTypesPackageJson("bar2" /*addTypesCondition*/, true),
"/home/src/projects/project/node_modules/@types/bar2/index.d.ts": getTscModuleResolutionAlternateResultDts("bar2"),
"/home/src/projects/project/node_modules/bar2/package.json": getTscModuleResolutionAlternateResultPackageJson("bar2" /*addTypes*/, false /*addTypesCondition*/, false),
"/home/src/projects/project/node_modules/bar2/index.js": getTscModuleResolutionAlternateResultJs("bar2"),
"/home/src/projects/project/node_modules/bar2/index.mjs": getTscModuleResolutionAlternateResultMjs("bar2"),
"/home/src/projects/project/node_modules/foo2/package.json": getTscModuleResolutionAlternateResultPackageJson("foo2" /*addTypes*/, true /*addTypesCondition*/, true),
"/home/src/projects/project/node_modules/foo2/index.js": getTscModuleResolutionAlternateResultJs("foo2"),
"/home/src/projects/project/node_modules/foo2/index.mjs": getTscModuleResolutionAlternateResultMjs("foo2"),
"/home/src/projects/project/node_modules/foo2/index.d.ts": getTscModuleResolutionAlternateResultDts("foo2"),
"/home/src/projects/project/index.mts": stringtestutil.Dedent(`
import { foo } from "foo";
import { bar } from "bar";
import { foo2 } from "foo2";
import { bar2 } from "bar2";
`),
"/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "node16",
"moduleResolution": "node16",
"traceResolution": true,
"incremental": true,
"strict": true,
"types": [],
},
"files": ["index.mts"],
}`),
},
cwd: "/home/src/projects/project",
edits: []*tscEdit{
{
caption: "delete the alternateResult in @types",
edit: func(sys *testSys) {
sys.removeNoError("/home/src/projects/project/node_modules/@types/bar/index.d.ts")
},
// !!! repopulateInfo on diagnostics not yet implemented
expectedDiff: "Currently we arent repopulating error chain so errors will be different",
},
{
caption: "delete the node10Result in package/types",
edit: func(sys *testSys) {
sys.removeNoError("/home/src/projects/project/node_modules/foo/index.d.ts")
},
// !!! repopulateInfo on diagnostics not yet implemented
expectedDiff: "Currently we arent repopulating error chain so errors will be different",
},
{
caption: "add the alternateResult in @types",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/node_modules/@types/bar/index.d.ts", getTscModuleResolutionAlternateResultDts("bar"), false)
},
// !!! repopulateInfo on diagnostics not yet implemented
expectedDiff: "Currently we arent repopulating error chain so errors will be different",
},
{
caption: "add the alternateResult in package/types",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/node_modules/foo/index.d.ts", getTscModuleResolutionAlternateResultDts("foo"), false)
},
},
{
caption: "update package.json from @types so error is fixed",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/node_modules/@types/bar/package.json", getTscModuleResolutionAlternateResultAtTypesPackageJson("bar" /*addTypesCondition*/, true), false)
},
},
{
caption: "update package.json so error is fixed",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/node_modules/foo/package.json", getTscModuleResolutionAlternateResultPackageJson("foo" /*addTypes*/, true /*addTypesCondition*/, true), false)
},
},
{
caption: "update package.json from @types so error is introduced",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/node_modules/@types/bar2/package.json", getTscModuleResolutionAlternateResultAtTypesPackageJson("bar2" /*addTypesCondition*/, false), false)
},
},
{
caption: "update package.json so error is introduced",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/node_modules/foo2/package.json", getTscModuleResolutionAlternateResultPackageJson("foo2" /*addTypes*/, true /*addTypesCondition*/, false), false)
},
},
{
caption: "delete the alternateResult in @types",
edit: func(sys *testSys) {
sys.removeNoError("/home/src/projects/project/node_modules/@types/bar2/index.d.ts")
},
// !!! repopulateInfo on diagnostics not yet implemented
expectedDiff: "Currently we arent repopulating error chain so errors will be different",
},
{
caption: "delete the node10Result in package/types",
edit: func(sys *testSys) {
sys.removeNoError("/home/src/projects/project/node_modules/foo2/index.d.ts")
},
// !!! repopulateInfo on diagnostics not yet implemented
expectedDiff: "Currently we arent repopulating error chain so errors will be different",
},
{
caption: "add the alternateResult in @types",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/node_modules/@types/bar2/index.d.ts", getTscModuleResolutionAlternateResultDts("bar2"), false)
},
// !!! repopulateInfo on diagnostics not yet implemented
expectedDiff: "Currently we arent repopulating error chain so errors will be different",
},
{
caption: "add the ndoe10Result in package/types",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/node_modules/foo2/index.d.ts", getTscModuleResolutionAlternateResultDts("foo2"), false)
},
},
},
},
{
subScenario: "handles the cache correctly when two projects use different module resolution settings",
files: FileMap{
`/user/username/projects/myproject/project1/index.ts`: `import { foo } from "file";`,
`/user/username/projects/myproject/project1/node_modules/file/index.d.ts`: "export const foo = 10;",
`/user/username/projects/myproject/project1/tsconfig.json`: stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"types": ["foo", "bar"]
},
"files": ["index.ts"],
}`),
`/user/username/projects/myproject/project2/index.ts`: `import { foo } from "file";`,
`/user/username/projects/myproject/project2/node_modules/file/index.d.ts`: "export const foo = 10;",
`/user/username/projects/myproject/project2/tsconfig.json`: stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"types": ["foo"],
"module": "nodenext",
"moduleResolution": "nodenext"
},
"files": ["index.ts"],
}`),
`/user/username/projects/myproject/node_modules/@types/foo/index.d.ts`: "export const foo = 10;",
`/user/username/projects/myproject/node_modules/@types/bar/index.d.ts`: "export const bar = 10;",
`/user/username/projects/myproject/tsconfig.json`: stringtestutil.Dedent(`
{
"files": [],
"references": [
{ "path": "./project1" },
{ "path": "./project2" },
],
}`),
},
cwd: "/user/username/projects/myproject",
commandLineArgs: []string{"--b", "-w", "-v"},
edits: []*tscEdit{
{
caption: "Append text",
edit: func(sys *testSys) {
sys.appendFile(`/user/username/projects/myproject/project1/index.ts`, "const bar = 10;")
},
},
},
},
{
// !!! sheetal package.json watches not yet implemented
subScenario: `resolves specifier in output declaration file from referenced project correctly with cts and mts extensions`,
files: FileMap{
`/user/username/projects/myproject/packages/pkg1/package.json`: stringtestutil.Dedent(`
{
"name": "pkg1",
"version": "1.0.0",
"main": "build/index.js",
"type": "module"
}`),
`/user/username/projects/myproject/packages/pkg1/index.ts`: stringtestutil.Dedent(`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`),
`/user/username/projects/myproject/packages/pkg1/tsconfig.json`: stringtestutil.Dedent(`
{
"compilerOptions": {
"outDir": "build",
"module": "node16",
},
"references": [{ "path": "../pkg2" }],
}`),
`/user/username/projects/myproject/packages/pkg2/const.cts`: `export type TheNum = 42;`,
`/user/username/projects/myproject/packages/pkg2/index.ts`: `export type { TheNum } from './const.cjs';`,
`/user/username/projects/myproject/packages/pkg2/tsconfig.json`: stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "build",
"module": "node16",
},
}`),
`/user/username/projects/myproject/packages/pkg2/package.json`: stringtestutil.Dedent(`
{
"name": "pkg2",
"version": "1.0.0",
"main": "build/index.js",
"type": "module"
}`),
`/user/username/projects/myproject/node_modules/pkg2`: vfstest.Symlink(`/user/username/projects/myproject/packages/pkg2`),
},
cwd: "/user/username/projects/myproject",
commandLineArgs: []string{"-b", "packages/pkg1", "-w", "--verbose", "--traceResolution"},
edits: []*tscEdit{
{
caption: "reports import errors after change to package file",
edit: func(sys *testSys) {
sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"module"`, `"commonjs"`)
},
expectedDiff: "Package.json watch pending, so no change detected yet",
},
{
caption: "removes those errors when a package file is changed back",
edit: func(sys *testSys) {
sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"commonjs"`, `"module"`)
},
},
{
caption: "reports import errors after change to package file",
edit: func(sys *testSys) {
sys.replaceFileText(`/user/username/projects/myproject/packages/pkg1/package.json`, `"module"`, `"commonjs"`)
},
expectedDiff: "Package.json watch pending, so no change detected yet",
},
{
caption: "removes those errors when a package file is changed to cjs extensions",
edit: func(sys *testSys) {
sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `"build/index.js"`, `"build/index.cjs"`)
sys.renameFileNoError(`/user/username/projects/myproject/packages/pkg2/index.ts`, `/user/username/projects/myproject/packages/pkg2/index.cts`)
},
},
},
},
{
subScenario: `build mode watches for changes to package-json main fields`,
files: FileMap{
`/user/username/projects/myproject/packages/pkg1/package.json`: stringtestutil.Dedent(`
{
"name": "pkg1",
"version": "1.0.0",
"main": "build/index.js"
}`),
`/user/username/projects/myproject/packages/pkg1/index.ts`: stringtestutil.Dedent(`
import type { TheNum } from 'pkg2'
export const theNum: TheNum = 42;`),
`/user/username/projects/myproject/packages/pkg1/tsconfig.json`: stringtestutil.Dedent(`
{
"compilerOptions": {
"outDir": "build",
},
"references": [{ "path": "../pkg2" }],
}`),
`/user/username/projects/myproject/packages/pkg2/tsconfig.json`: stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "build",
},
}`),
`/user/username/projects/myproject/packages/pkg2/const.ts`: `export type TheNum = 42;`,
`/user/username/projects/myproject/packages/pkg2/index.ts`: `export type { TheNum } from './const.js';`,
`/user/username/projects/myproject/packages/pkg2/other.ts`: `export type TheStr = string;`,
`/user/username/projects/myproject/packages/pkg2/package.json`: stringtestutil.Dedent(`
{
"name": "pkg2",
"version": "1.0.0",
"main": "build/index.js"
}`),
`/user/username/projects/myproject/node_modules/pkg2`: vfstest.Symlink(`/user/username/projects/myproject/packages/pkg2`),
},
cwd: "/user/username/projects/myproject",
commandLineArgs: []string{"-b", "packages/pkg1", "--verbose", "-w", "--traceResolution"},
edits: []*tscEdit{
{
caption: "reports import errors after change to package file",
edit: func(sys *testSys) {
sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `index.js`, `other.js`)
},
expectedDiff: "Package.json watch pending, so no change detected yet",
},
{
caption: "removes those errors when a package file is changed back",
edit: func(sys *testSys) {
sys.replaceFileText(`/user/username/projects/myproject/packages/pkg2/package.json`, `other.js`, `index.js`)
},
},
},
},
{
subScenario: "resolution from d.ts of referenced project",
files: FileMap{
"/home/src/workspaces/project/common.d.ts": "export type OnValue = (value: number) => void",
"/home/src/workspaces/project/producer/index.ts": stringtestutil.Dedent(`
export { ValueProducerDeclaration } from "./in-js"
import { OnValue } from "@common"
export interface ValueProducerFromTs {
onValue: OnValue;
}
`),
"/home/src/workspaces/project/producer/in-js.d.ts": stringtestutil.Dedent(`
import { OnValue } from "@common"
export interface ValueProducerDeclaration {
onValue: OnValue;
}
`),
"/home/src/workspaces/project/producer/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"strict": true,
"composite": true,
"module": "nodenext",
"moduleResolution": "nodenext",
"paths": {
"@common": ["../common.d.ts"],
},
},
}`),
"/home/src/workspaces/project/consumer/index.ts": stringtestutil.Dedent(`
import { ValueProducerDeclaration, ValueProducerFromTs } from "@producer"
declare let v: ValueProducerDeclaration;
// n is implicitly any because onValue is actually any (despite what the tooltip says)
v.onValue = (n) => {
}
// n is implicitly number as expected
declare let v2: ValueProducerFromTs;
v2.onValue = (n) => {
}`),
"/home/src/workspaces/project/consumer/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"strict": true,
"module": "nodenext",
"moduleResolution": "nodenext",
"paths": {
"@producer": ["../producer/index"],
},
},
"references": [
{ "path": "../producer" },
],
}`),
},
commandLineArgs: []string{"--b", "consumer", "--traceResolution", "-v"},
},
}
for _, test := range testCases {
test.run(t, "moduleResolution")
}
}
func TestTscNoCheck(t *testing.T) {
t.Parallel()
type noCheckScenario struct {
subScenario string
aText string
}
getTscNoCheckTestCase := func(scenario *noCheckScenario, incremental bool, commandLineArgs []string) *tscInput {
noChangeWithCheck := &tscEdit{
caption: "No Change run with checking",
commandLineArgs: commandLineArgs,
}
fixErrorNoCheck := &tscEdit{
caption: "Fix `a` error with noCheck",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/workspaces/project/a.ts", `export const a = "hello";`, false)
},
}
addErrorNoCheck := &tscEdit{
caption: "Introduce error with noCheck",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/workspaces/project/a.ts", scenario.aText, false)
},
}
return &tscInput{
subScenario: scenario.subScenario + core.IfElse(incremental, " with incremental", ""),
files: FileMap{
"/home/src/workspaces/project/a.ts": scenario.aText,
"/home/src/workspaces/project/b.ts": `export const b = 10;`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"declaration": true,
"incremental": %t
}
}`, incremental)),
},
commandLineArgs: slices.Concat(commandLineArgs, []string{"--noCheck"}),
edits: []*tscEdit{
noChange,
fixErrorNoCheck, // Fix error with noCheck
noChange, // Should be no op
noChangeWithCheck, // Check errors - should not report any errors - update buildInfo
noChangeWithCheck, // Should be no op
noChange, // Should be no op
addErrorNoCheck,
noChange, // Should be no op
noChangeWithCheck, // Should check errors and update buildInfo
fixErrorNoCheck, // Fix error with noCheck
noChangeWithCheck, // Should check errors and update buildInfo
{
caption: "Add file with error",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/workspaces/project/c.ts", `export const c: number = "hello";`, false)
},
commandLineArgs: commandLineArgs,
},
addErrorNoCheck,
fixErrorNoCheck,
noChangeWithCheck,
noChange, // Should be no op
noChangeWithCheck, // Should be no op
},
}
}
cases := []noCheckScenario{
{"syntax errors", `export const a = "hello`},
{"semantic errors", `export const a: number = "hello";`},
{"dts errors", `export const a = class { private p = 10; };`},
}
testCases := core.FlatMap(cases, func(c noCheckScenario) []*tscInput {
return []*tscInput{
getTscNoCheckTestCase(&c, false, []string{}),
getTscNoCheckTestCase(&c, true, []string{}),
getTscNoCheckTestCase(&c, false, []string{"-b", "-v"}),
getTscNoCheckTestCase(&c, true, []string{"-b", "-v"}),
}
})
for _, test := range testCases {
test.run(t, "noCheck")
}
}
func TestTscNoEmit(t *testing.T) {
t.Parallel()
type tscNoEmitScenario struct {
subScenario string
aText string
dtsEnabled bool
}
noEmitScenarios := []*tscNoEmitScenario{
{
subScenario: "syntax errors",
aText: `const a = "hello`,
},
{
subScenario: "semantic errors",
aText: `const a: number = "hello"`,
},
{
subScenario: "dts errors",
aText: `const a = class { private p = 10; };`,
dtsEnabled: true,
},
{
subScenario: "dts errors without dts enabled",
aText: `const a = class { private p = 10; };`,
},
}
getTscNoEmitAndErrorsFileMap := func(scenario *tscNoEmitScenario, incremental bool, asModules bool, modify func(FileMap)) FileMap {
files := FileMap{
"/home/src/projects/project/a.ts": core.IfElse(asModules, `export `, "") + scenario.aText,
"/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"incremental": %t,
"declaration": %t
}
}
`, incremental, scenario.dtsEnabled)),
}
if asModules {
files["/home/src/projects/project/b.ts"] = `export const b = 10;`
}
if modify != nil {
modify(files)
}
return files
}
getTscNoEmitAndErrorsTestCasesWorker := func(commandLineArgs []string, addNoEmitOnCommandLine bool, modify func(FileMap), edits func(scenario *tscNoEmitScenario, commandLineArgs []string, asModules bool) []*tscEdit) []*tscInput {
testingCases := make([]*tscInput, 0, len(noEmitScenarios)*3)
commandLineArgsForInput := commandLineArgs
if addNoEmitOnCommandLine {
commandLineArgsForInput = slices.Concat(commandLineArgs, []string{"--noEmit"})
}
for _, scenario := range noEmitScenarios {
testingCases = append(
testingCases,
&tscInput{
subScenario: scenario.subScenario,
commandLineArgs: commandLineArgsForInput,
files: getTscNoEmitAndErrorsFileMap(scenario, false, false, modify),
cwd: "/home/src/projects/project",
edits: edits(scenario, commandLineArgs, false),
},
&tscInput{
subScenario: scenario.subScenario + " with incremental",
commandLineArgs: commandLineArgsForInput,
files: getTscNoEmitAndErrorsFileMap(scenario, true, false, modify),
cwd: "/home/src/projects/project",
edits: edits(scenario, commandLineArgs, false),
},
&tscInput{
subScenario: scenario.subScenario + " with incremental as modules",
commandLineArgs: commandLineArgsForInput,
files: getTscNoEmitAndErrorsFileMap(scenario, true, true, modify),
cwd: "/home/src/projects/project",
edits: edits(scenario, commandLineArgs, true),
},
)
}
return testingCases
}
getTscNoEmitAndErrorsTestCases := func(commandLineArgs []string) []*tscInput {
return getTscNoEmitAndErrorsTestCasesWorker(
commandLineArgs,
true,
nil,
func(scenario *tscNoEmitScenario, commandLineArgs []string, asModules bool) []*tscEdit {
fixedATsContent := core.IfElse(asModules, "export ", "") + `const a = "hello";`
return []*tscEdit{
noChange,
{
caption: "Fix error",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/a.ts", fixedATsContent, false)
},
},
noChange,
{
caption: "Emit after fixing error",
commandLineArgs: commandLineArgs,
},
noChange,
{
caption: "Introduce error",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/a.ts", scenario.aText, false)
},
},
{
caption: "Emit when error",
commandLineArgs: commandLineArgs,
},
noChange,
}
},
)
}
getTscNoEmitAndErrorsWatchTestCases := func(commandLineArgs []string) []*tscInput {
return getTscNoEmitAndErrorsTestCasesWorker(
commandLineArgs,
false,
func(files FileMap) {
files["/home/src/projects/project/tsconfig.json"] = strings.Replace(files["/home/src/projects/project/tsconfig.json"].(string), "}", `, "noEmit": true }`, 1)
},
func(scenario *tscNoEmitScenario, commandLineArgs []string, asModules bool) []*tscEdit {
fixedATsContent := core.IfElse(asModules, "export ", "") + `const a = "hello";`
return []*tscEdit{
{
caption: "Fix error",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/a.ts", fixedATsContent, false)
},
},
{
caption: "Emit after fixing error",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/projects/project/tsconfig.json", `"noEmit": true`, `"noEmit": false`)
},
},
{
caption: "no Emit run after fixing error",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/projects/project/tsconfig.json", `"noEmit": false`, `"noEmit": true`)
},
},
{
caption: "Introduce error",
edit: func(sys *testSys) {
sys.writeFileNoError("/home/src/projects/project/a.ts", scenario.aText, false)
},
},
{
caption: "Emit when error",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/projects/project/tsconfig.json", `"noEmit": true`, `"noEmit": false`)
},
},
{
caption: "no Emit run when error",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/projects/project/tsconfig.json", `"noEmit": false`, `"noEmit": true`)
},
},
}
},
)
}
getTscNoEmitChangesFileMap := func(optionsStr string) FileMap {
return FileMap{
"/home/src/workspaces/project/src/class.ts": stringtestutil.Dedent(`
export class classC {
prop = 1;
}`),
"/home/src/workspaces/project/src/indirectClass.ts": stringtestutil.Dedent(`
import { classC } from './class';
export class indirectClass {
classC = new classC();
}`),
"/home/src/workspaces/project/src/directUse.ts": stringtestutil.Dedent(`
import { indirectClass } from './indirectClass';
new indirectClass().classC.prop;`),
"/home/src/workspaces/project/src/indirectUse.ts": stringtestutil.Dedent(`
import { indirectClass } from './indirectClass';
new indirectClass().classC.prop;`),
"/home/src/workspaces/project/src/noChangeFile.ts": stringtestutil.Dedent(`
export function writeLog(s: string) {
}`),
"/home/src/workspaces/project/src/noChangeFileWithEmitSpecificError.ts": stringtestutil.Dedent(`
function someFunc(arguments: boolean, ...rest: any[]) {
}`),
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": { %s }
}`, optionsStr)),
}
}
type tscNoEmitChangesScenario struct {
subScenario string
optionsString string
}
noEmitChangesScenarios := []*tscNoEmitChangesScenario{
{
// !!! sheetal missing initial reporting of Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters is absent
subScenario: "composite",
optionsString: `"composite": true`,
},
{
subScenario: "incremental declaration",
optionsString: `"incremental": true, "declaration": true`,
},
{
subScenario: "incremental",
optionsString: `"incremental": true`,
},
}
getTscNoEmitChangesTestCases := func(commandLineArgs []string) []*tscInput {
noChangeWithNoEmit := &tscEdit{
caption: "No Change run with noEmit",
commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit"}),
}
noChangeWithEmit := &tscEdit{
caption: "No Change run with emit",
commandLineArgs: commandLineArgs,
}
introduceError := func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/src/class.ts", "prop", "prop1")
}
fixError := func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/src/class.ts", "prop1", "prop")
}
testCases := make([]*tscInput, 0, len(noEmitChangesScenarios))
for _, scenario := range noEmitChangesScenarios {
testCases = append(
testCases,
&tscInput{
subScenario: "changes " + scenario.subScenario,
commandLineArgs: commandLineArgs,
files: getTscNoEmitChangesFileMap(scenario.optionsString),
edits: []*tscEdit{
noChangeWithNoEmit,
noChangeWithNoEmit,
{
caption: "Introduce error but still noEmit",
commandLineArgs: noChangeWithNoEmit.commandLineArgs,
edit: introduceError,
},
{
caption: "Fix error and emit",
edit: fixError,
},
noChangeWithEmit,
noChangeWithNoEmit,
noChangeWithNoEmit,
noChangeWithEmit,
{
caption: "Introduce error and emit",
edit: introduceError,
},
noChangeWithEmit,
noChangeWithNoEmit,
noChangeWithNoEmit,
noChangeWithEmit,
{
caption: "Fix error and no emit",
commandLineArgs: noChangeWithNoEmit.commandLineArgs,
edit: fixError,
},
noChangeWithEmit,
noChangeWithNoEmit,
noChangeWithNoEmit,
noChangeWithEmit,
},
},
&tscInput{
subScenario: "changes with initial noEmit " + scenario.subScenario,
commandLineArgs: noChangeWithNoEmit.commandLineArgs,
files: getTscNoEmitChangesFileMap(scenario.optionsString),
edits: []*tscEdit{
noChangeWithEmit,
{
caption: "Introduce error with emit",
commandLineArgs: commandLineArgs,
edit: introduceError,
},
{
caption: "Fix error and no emit",
edit: fixError,
},
noChangeWithEmit,
},
},
)
}
return testCases
}
getTscNoEmitDtsChangesFileMap := func(incremental bool, asModules bool) FileMap {
files := FileMap{
"/home/src/projects/project/a.ts": core.IfElse(asModules, `export const a = class { private p = 10; };`, `const a = class { private p = 10; };`),
"/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"incremental": %t,
}
}
`, incremental)),
}
if asModules {
files["/home/src/projects/project/b.ts"] = `export const b = 10;`
}
return files
}
getTscNoEmitDtsChangesEdits := func(commandLineArgs []string) []*tscEdit {
return []*tscEdit{
noChange,
{
caption: "With declaration enabled noEmit - Should report errors",
commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit", "--declaration"}),
},
{
caption: "With declaration and declarationMap noEmit - Should report errors",
commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit", "--declaration", "--declarationMap"}),
},
noChange,
{
caption: "Dts Emit with error",
commandLineArgs: slices.Concat(commandLineArgs, []string{"--declaration"}),
},
{
caption: "Fix the error",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/projects/project/a.ts", "private", "public")
},
},
{
caption: "With declaration enabled noEmit",
commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit", "--declaration"}),
},
{
caption: "With declaration and declarationMap noEmit",
commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit", "--declaration", "--declarationMap"}),
},
}
}
getTscNoEmitDtsChangesTestCases := func() []*tscInput {
return []*tscInput{
{
subScenario: "dts errors with declaration enable changes",
commandLineArgs: []string{"-b", "-v", "--noEmit"},
files: getTscNoEmitDtsChangesFileMap(false, false),
cwd: "/home/src/projects/project",
edits: getTscNoEmitDtsChangesEdits([]string{"-b", "-v"}),
},
{
subScenario: "dts errors with declaration enable changes with incremental",
commandLineArgs: []string{"-b", "-v", "--noEmit"},
files: getTscNoEmitDtsChangesFileMap(true, false),
cwd: "/home/src/projects/project",
edits: getTscNoEmitDtsChangesEdits([]string{"-b", "-v"}),
},
{
subScenario: "dts errors with declaration enable changes with incremental as modules",
commandLineArgs: []string{"-b", "-v", "--noEmit"},
files: getTscNoEmitDtsChangesFileMap(true, true),
cwd: "/home/src/projects/project",
edits: getTscNoEmitDtsChangesEdits([]string{"-b", "-v"}),
},
}
}
getTscNoEmitDtsChangesMultiFileErrorsTestCases := func(commandLineArgs []string) []*tscInput {
aContent := `export const a = class { private p = 10; };`
return []*tscInput{
{
subScenario: "dts errors with declaration enable changes with multiple files",
commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit"}),
files: FileMap{
"/home/src/projects/project/a.ts": aContent,
"/home/src/projects/project/b.ts": `export const b = 10;`,
"/home/src/projects/project/c.ts": strings.Replace(aContent, "a", "c", 1),
"/home/src/projects/project/d.ts": strings.Replace(aContent, "a", "d", 1),
"/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"incremental": true,
}
}
`),
},
cwd: "/home/src/projects/project",
edits: slices.Concat(
getTscNoEmitDtsChangesEdits(commandLineArgs),
[]*tscEdit{
{
caption: "Fix the another ",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/projects/project/c.ts", "private", "public")
},
commandLineArgs: slices.Concat(commandLineArgs, []string{"--noEmit", "--declaration", "--declarationMap"}),
},
},
),
},
}
}
getTscNoEmitLoopTestCase := func(suffix string, commandLineArgs []string) *tscInput {
return &tscInput{
subScenario: "does not go in loop when watching when no files are emitted" + suffix,
files: FileMap{
"/user/username/projects/myproject/a.js": "",
"/user/username/projects/myproject/b.ts": "",
"/user/username/projects/myproject/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"allowJs": true,
"noEmit": true,
},
}`),
},
cwd: "/user/username/projects/myproject",
commandLineArgs: commandLineArgs,
edits: []*tscEdit{
{
caption: "No change",
edit: func(sys *testSys) {
sys.writeFileNoError(`/user/username/projects/myproject/a.js`, sys.readFileNoError(`/user/username/projects/myproject/a.js`), false)
},
},
{
caption: "change",
edit: func(sys *testSys) {
sys.writeFileNoError(`/user/username/projects/myproject/a.js`, "const x = 10;", false)
},
},
},
}
}
testCases := slices.Concat(
[]*tscInput{
{
subScenario: "when project has strict true",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"incremental": true,
"strict": true
}
}`),
"/home/src/workspaces/project/class1.ts": `export class class1 {}`,
},
commandLineArgs: []string{"--noEmit"},
edits: noChangeOnlyEdit,
},
getTscNoEmitLoopTestCase("", []string{"-b", "-w", "-verbose"}),
getTscNoEmitLoopTestCase(" with incremental", []string{"-b", "-w", "-verbose", "--incremental"}),
},
getTscNoEmitAndErrorsTestCases([]string{}),
getTscNoEmitAndErrorsTestCases([]string{"-b", "-v"}),
getTscNoEmitChangesTestCases([]string{}),
getTscNoEmitChangesTestCases([]string{"-b", "-v"}),
getTscNoEmitDtsChangesTestCases(),
getTscNoEmitDtsChangesMultiFileErrorsTestCases([]string{}),
getTscNoEmitDtsChangesMultiFileErrorsTestCases([]string{"-b", "-v"}),
getTscNoEmitAndErrorsWatchTestCases([]string{"-b", "-verbose", "-w"}),
)
for _, test := range testCases {
test.run(t, "noEmit")
}
}
func TestTscNoEmitOnError(t *testing.T) {
t.Parallel()
type tscNoEmitOnErrorScenario struct {
subScenario string
mainErrorContent string
fixedErrorContent string
}
getTscNoEmitOnErrorFileMap := func(scenario *tscNoEmitOnErrorScenario, declaration bool, incremental bool) FileMap {
return FileMap{
"/user/username/projects/noEmitOnError/tsconfig.json": stringtestutil.Dedent(fmt.Sprintf(`
{
"compilerOptions": {
"outDir": "./dev-build",
"declaration": %t,
"incremental": %t,
"noEmitOnError": true,
},
}`, declaration, incremental)),
"/user/username/projects/noEmitOnError/shared/types/db.ts": stringtestutil.Dedent(`
export interface A {
name: string;
}
`),
"/user/username/projects/noEmitOnError/src/main.ts": scenario.mainErrorContent,
"/user/username/projects/noEmitOnError/src/other.ts": stringtestutil.Dedent(`
console.log("hi");
export { }
`),
}
}
getTscNoEmitOnErrorTestCases := func(scenarios []*tscNoEmitOnErrorScenario, commandLineArgs []string) []*tscInput {
testCases := make([]*tscInput, 0, len(scenarios)*4)
for _, scenario := range scenarios {
edits := []*tscEdit{
noChange,
{
caption: "Fix error",
edit: func(sys *testSys) {
sys.writeFileNoError("/user/username/projects/noEmitOnError/src/main.ts", scenario.fixedErrorContent, false)
},
},
noChange,
}
testCases = append(
testCases,
&tscInput{
subScenario: scenario.subScenario,
files: getTscNoEmitOnErrorFileMap(scenario, false, false),
cwd: "/user/username/projects/noEmitOnError",
commandLineArgs: commandLineArgs,
edits: edits,
},
&tscInput{
subScenario: scenario.subScenario + " with declaration",
files: getTscNoEmitOnErrorFileMap(scenario, true, false),
cwd: "/user/username/projects/noEmitOnError",
commandLineArgs: commandLineArgs,
edits: edits,
},
&tscInput{
subScenario: scenario.subScenario + " with incremental",
files: getTscNoEmitOnErrorFileMap(scenario, false, true),
cwd: "/user/username/projects/noEmitOnError",
commandLineArgs: commandLineArgs,
edits: edits,
},
&tscInput{
subScenario: scenario.subScenario + " with declaration with incremental",
files: getTscNoEmitOnErrorFileMap(scenario, true, true),
cwd: "/user/username/projects/noEmitOnError",
commandLineArgs: commandLineArgs,
edits: edits,
},
)
}
return testCases
}
getTscWatchNoEmitOnErrorTestCases := func(scenarios []*tscNoEmitOnErrorScenario, commandLineArgs []string) []*tscInput {
var edits []*tscEdit
for _, scenario := range scenarios {
if edits != nil {
edits = append(edits, &tscEdit{
caption: scenario.subScenario,
edit: func(sys *testSys) {
sys.writeFileNoError(`/user/username/projects/noEmitOnError/src/main.ts`, scenario.mainErrorContent, false)
},
})
}
edits = append(edits,
&tscEdit{
caption: "No Change",
edit: func(sys *testSys) {
sys.writeFileNoError(`/user/username/projects/noEmitOnError/src/main.ts`, sys.readFileNoError(`/user/username/projects/noEmitOnError/src/main.ts`), false)
},
},
&tscEdit{
caption: "Fix " + scenario.subScenario,
edit: func(sys *testSys) {
sys.writeFileNoError("/user/username/projects/noEmitOnError/src/main.ts", scenario.fixedErrorContent, false)
},
},
&tscEdit{
caption: "No Change",
edit: func(sys *testSys) {
sys.writeFileNoError(`/user/username/projects/noEmitOnError/src/main.ts`, sys.readFileNoError(`/user/username/projects/noEmitOnError/src/main.ts`), false)
},
},
)
}
return []*tscInput{
{
subScenario: "noEmitOnError",
files: getTscNoEmitOnErrorFileMap(scenarios[0], false, false),
cwd: "/user/username/projects/noEmitOnError",
commandLineArgs: commandLineArgs,
edits: edits,
},
{
subScenario: "noEmitOnError with declaration",
files: getTscNoEmitOnErrorFileMap(scenarios[0], true, false),
cwd: "/user/username/projects/noEmitOnError",
commandLineArgs: commandLineArgs,
edits: edits,
},
{
subScenario: "noEmitOnError with incremental",
files: getTscNoEmitOnErrorFileMap(scenarios[0], false, true),
cwd: "/user/username/projects/noEmitOnError",
commandLineArgs: commandLineArgs,
edits: edits,
},
{
subScenario: "noEmitOnError with declaration with incremental",
files: getTscNoEmitOnErrorFileMap(scenarios[0], true, true),
cwd: "/user/username/projects/noEmitOnError",
commandLineArgs: commandLineArgs,
edits: edits,
},
}
}
scenarios := []*tscNoEmitOnErrorScenario{
{
subScenario: "syntax errors",
mainErrorContent: stringtestutil.Dedent(`
import { A } from "../shared/types/db";
const a = {
lastName: 'sdsd'
;
`),
fixedErrorContent: stringtestutil.Dedent(`
import { A } from "../shared/types/db";
const a = {
lastName: 'sdsd'
};`),
},
{
subScenario: "semantic errors",
mainErrorContent: stringtestutil.Dedent(`
import { A } from "../shared/types/db";
const a: string = 10;`),
fixedErrorContent: stringtestutil.Dedent(`
import { A } from "../shared/types/db";
const a: string = "hello";`),
},
{
subScenario: "dts errors",
mainErrorContent: stringtestutil.Dedent(`
import { A } from "../shared/types/db";
export const a = class { private p = 10; };
`),
fixedErrorContent: stringtestutil.Dedent(`
import { A } from "../shared/types/db";
export const a = class { p = 10; };
`),
},
}
testCases := slices.Concat(
getTscNoEmitOnErrorTestCases(scenarios, []string{}),
getTscNoEmitOnErrorTestCases(scenarios, []string{"-b", "-v"}),
[]*tscInput{
{
subScenario: `when declarationMap changes`,
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"noEmitOnError": true,
"declaration": true,
"composite": true,
},
}`),
"/home/src/workspaces/project/a.ts": "const x = 10;",
"/home/src/workspaces/project/b.ts": "const y = 10;",
},
edits: []*tscEdit{
{
caption: "error and enable declarationMap",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/a.ts", "x", "x: 20")
},
commandLineArgs: []string{"--declarationMap"},
},
{
caption: "fix error declarationMap",
edit: func(sys *testSys) {
sys.replaceFileText("/home/src/workspaces/project/a.ts", "x: 20", "x")
},
commandLineArgs: []string{"--declarationMap"},
},
},
},
{
subScenario: "file deleted before fixing error with noEmitOnError",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"outDir": "outDir",
"noEmitOnError": true,
},
}`),
"/home/src/workspaces/project/file1.ts": `export const x: 30 = "hello";`,
"/home/src/workspaces/project/file2.ts": `export class D { }`,
},
commandLineArgs: []string{"-i"},
edits: []*tscEdit{
{
caption: "delete file without error",
edit: func(sys *testSys) {
sys.removeNoError("/home/src/workspaces/project/file2.ts")
},
},
},
},
},
getTscWatchNoEmitOnErrorTestCases(scenarios, []string{"-b", "-w", "-v"}),
)
for _, test := range testCases {
test.run(t, "noEmitOnError")
}
}
func TestTscProjectReferences(t *testing.T) {
t.Parallel()
cases := []tscInput{
{
subScenario: "when project references composite project with noEmit",
files: FileMap{
"/home/src/workspaces/solution/utils/index.ts": "export const x = 10;",
"/home/src/workspaces/solution/utils/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"noEmit": true
}
}`),
"/home/src/workspaces/solution/project/index.ts": `import { x } from "../utils";`,
"/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(`
{
"references": [
{ "path": "../utils" },
],
}`),
},
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--p", "project"},
},
{
subScenario: "when project references composite",
files: FileMap{
"/home/src/workspaces/solution/utils/index.ts": "export const x = 10;",
"/home/src/workspaces/solution/utils/index.d.ts": "export declare const x = 10;",
"/home/src/workspaces/solution/utils/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true
}
}`),
"/home/src/workspaces/solution/project/index.ts": `import { x } from "../utils";`,
"/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(`
{
"references": [
{ "path": "../utils" },
],
}`),
},
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--p", "project"},
},
{
subScenario: "when project reference is not built",
files: FileMap{
"/home/src/workspaces/solution/utils/index.ts": "export const x = 10;",
"/home/src/workspaces/solution/utils/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true
}
}`),
"/home/src/workspaces/solution/project/index.ts": `import { x } from "../utils";`,
"/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(`
{
"references": [
{ "path": "../utils" },
],
}`),
},
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--p", "project"},
},
{
subScenario: "when project contains invalid project reference",
files: FileMap{
"/home/src/workspaces/solution/project/index.ts": `export const x = 10;`,
"/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(`
{
"references": [
{ "path": "../utils" },
],
}`),
},
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--p", "project"},
},
{
subScenario: "default import interop uses referenced project settings",
files: FileMap{
"/home/src/workspaces/project/node_modules/ambiguous-package/package.json": stringtestutil.Dedent(`
{
"name": "ambiguous-package"
}`),
"/home/src/workspaces/project/node_modules/ambiguous-package/index.d.ts": "export declare const ambiguous: number;",
"/home/src/workspaces/project/node_modules/esm-package/package.json": stringtestutil.Dedent(`
{
"name": "esm-package",
"type": "module"
}`),
"/home/src/workspaces/project/node_modules/esm-package/index.d.ts": "export declare const esm: number;",
"/home/src/workspaces/project/lib/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"declaration": true,
"rootDir": "src",
"outDir": "dist",
"module": "esnext",
"moduleResolution": "bundler",
},
"include": ["src"],
}`),
"/home/src/workspaces/project/lib/src/a.ts": "export const a = 0;",
"/home/src/workspaces/project/lib/dist/a.d.ts": "export declare const a = 0;",
"/home/src/workspaces/project/app/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"rootDir": "src",
"outDir": "dist",
},
"include": ["src"],
"references": [
{ "path": "../lib" },
],
}`),
"/home/src/workspaces/project/app/src/local.ts": "export const local = 0;",
"/home/src/workspaces/project/app/src/index.ts": stringtestutil.Dedent(`
import local from "./local"; // Error
import esm from "esm-package"; // Error
import referencedSource from "../../lib/src/a"; // Error
import referencedDeclaration from "../../lib/dist/a"; // Error
import ambiguous from "ambiguous-package"; // Ok`),
},
commandLineArgs: []string{"--p", "app", "--pretty", "false"},
},
{
subScenario: "referencing ambient const enum from referenced project with preserveConstEnums",
files: FileMap{
"/home/src/workspaces/solution/utils/index.ts": "export const enum E { A = 1 }",
"/home/src/workspaces/solution/utils/index.d.ts": "export declare const enum E { A = 1 }",
"/home/src/workspaces/solution/utils/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"declaration": true,
"preserveConstEnums": true,
},
}`),
"/home/src/workspaces/solution/project/index.ts": `import { E } from "../utils"; E.A;`,
"/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"isolatedModules": true,
},
"references": [
{ "path": "../utils" },
],
}`),
},
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--p", "project"},
},
{
subScenario: "importing const enum from referenced project with preserveConstEnums and verbatimModuleSyntax",
files: FileMap{
"/home/src/workspaces/solution/preserve/index.ts": "export const enum E { A = 1 }",
"/home/src/workspaces/solution/preserve/index.d.ts": "export declare const enum E { A = 1 }",
"/home/src/workspaces/solution/preserve/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"declaration": true,
"preserveConstEnums": true,
},
}`),
"/home/src/workspaces/solution/no-preserve/index.ts": "export const enum E { A = 1 }",
"/home/src/workspaces/solution/no-preserve/index.d.ts": "export declare const enum F { A = 1 }",
"/home/src/workspaces/solution/no-preserve/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"declaration": true,
"preserveConstEnums": false,
},
}`),
"/home/src/workspaces/solution/project/index.ts": stringtestutil.Dedent(`
import { E } from "../preserve";
import { F } from "../no-preserve";
E.A;
F.A;`),
"/home/src/workspaces/solution/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "preserve",
"verbatimModuleSyntax": true,
},
"references": [
{ "path": "../preserve" },
{ "path": "../no-preserve" },
],
}`),
},
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--p", "project", "--pretty", "false"},
},
{
subScenario: "rewriteRelativeImportExtensionsProjectReferences1",
files: FileMap{
"/home/src/workspaces/packages/common/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "dist",
"module": "nodenext"
}
}`),
"/home/src/workspaces/packages/common/package.json": stringtestutil.Dedent(`
{
"name": "common",
"version": "1.0.0",
"type": "module",
"exports": {
".": {
"source": "./src/index.ts",
"default": "./dist/index.js"
}
}
}`),
"/home/src/workspaces/packages/common/src/index.ts": "export {};",
"/home/src/workspaces/packages/common/dist/index.d.ts": "export {};",
"/home/src/workspaces/packages/main/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "nodenext",
"rewriteRelativeImportExtensions": true,
"rootDir": "src",
"outDir": "dist"
},
"references": [
{ "path": "../common" }
]
}`),
"/home/src/workspaces/packages/main/package.json": stringtestutil.Dedent(`
{
"type": "module"
}`),
"/home/src/workspaces/packages/main/src/index.ts": `import {} from "../../common/src/index.ts";`,
},
cwd: "/home/src/workspaces",
commandLineArgs: []string{"-p", "packages/main", "--pretty", "false"},
},
{
subScenario: "rewriteRelativeImportExtensionsProjectReferences2",
files: FileMap{
"/home/src/workspaces/solution/src/tsconfig-base.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "nodenext",
"composite": true,
"rootDir": ".",
"outDir": "../dist",
"rewriteRelativeImportExtensions": true
}
}`),
"/home/src/workspaces/solution/src/compiler/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../tsconfig-base.json",
"compilerOptions": {}
}`),
"/home/src/workspaces/solution/src/compiler/parser.ts": "export {};",
"/home/src/workspaces/solution/dist/compiler/parser.d.ts": "export {};",
"/home/src/workspaces/solution/src/services/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../tsconfig-base.json",
"compilerOptions": {},
"references": [
{ "path": "../compiler" }
]
}`),
"/home/src/workspaces/solution/src/services/services.ts": `import {} from "../compiler/parser.ts";`,
},
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--p", "src/services", "--pretty", "false"},
},
{
subScenario: "rewriteRelativeImportExtensionsProjectReferences3",
files: FileMap{
"/home/src/workspaces/solution/src/tsconfig-base.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"module": "nodenext",
"composite": true,
"rewriteRelativeImportExtensions": true
}
}`),
"/home/src/workspaces/solution/src/compiler/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../tsconfig-base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "../../dist/compiler"
}
}`),
"/home/src/workspaces/solution/src/compiler/parser.ts": "export {};",
"/home/src/workspaces/solution/dist/compiler/parser.d.ts": "export {};",
"/home/src/workspaces/solution/src/services/tsconfig.json": stringtestutil.Dedent(`
{
"extends": "../tsconfig-base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "../../dist/services"
},
"references": [
{ "path": "../compiler" }
]
}`),
"/home/src/workspaces/solution/src/services/services.ts": `import {} from "../compiler/parser.ts";`,
},
cwd: "/home/src/workspaces/solution",
commandLineArgs: []string{"--p", "src/services", "--pretty", "false"},
},
{
subScenario: "default setup was created correctly",
files: FileMap{
"/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
}
}`),
"/home/src/workspaces/project/primary/a.ts": "export { };",
"/home/src/workspaces/project/secondary/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
},
"references": [{
"path": "../primary"
}]
}`),
"/home/src/workspaces/project/secondary/b.ts": `import * as mod_1 from "../primary/a";`,
},
commandLineArgs: []string{"--p", "primary/tsconfig.json"},
},
{
subScenario: "errors when declaration = false",
files: FileMap{
"/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
"declaration": false
}
}`),
"/home/src/workspaces/project/primary/a.ts": "export { };",
},
commandLineArgs: []string{"--p", "primary/tsconfig.json"},
},
{
subScenario: "errors when the referenced project doesnt have composite",
files: FileMap{
"/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": false,
"outDir": "bin",
}
}`),
"/home/src/workspaces/project/primary/a.ts": "export { };",
"/home/src/workspaces/project/reference/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
},
"files": [ "b.ts" ],
"references": [ { "path": "../primary" } ]
}`),
"/home/src/workspaces/project/reference/b.ts": `import * as mod_1 from "../primary/a";`,
},
commandLineArgs: []string{"--p", "reference/tsconfig.json"},
},
{
subScenario: "does not error when the referenced project doesnt have composite if its a container project",
files: FileMap{
"/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": false,
"outDir": "bin",
}
}`),
"/home/src/workspaces/project/primary/a.ts": "export { };",
"/home/src/workspaces/project/reference/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
},
"files": [ ],
"references": [{
"path": "../primary"
}]
}`),
"/home/src/workspaces/project/reference/b.ts": `import * as mod_1 from "../primary/a";`,
},
commandLineArgs: []string{"--p", "reference/tsconfig.json"},
},
{
subScenario: "errors when the file list is not exhaustive",
files: FileMap{
"/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
},
"files": [ "a.ts" ]
}`),
"/home/src/workspaces/project/primary/a.ts": "import * as b from './b'",
"/home/src/workspaces/project/primary/b.ts": "export {}",
},
commandLineArgs: []string{"--p", "primary/tsconfig.json"},
},
{
subScenario: "errors when the referenced project doesnt exist",
files: FileMap{
"/home/src/workspaces/project/primary/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
},
"references": [{
"path": "../foo"
}]
}`),
"/home/src/workspaces/project/primary/a.ts": "export { };",
},
commandLineArgs: []string{"--p", "primary/tsconfig.json"},
},
{
subScenario: "redirects to the output dts file",
files: FileMap{
"/home/src/workspaces/project/alpha/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
}
}`),
"/home/src/workspaces/project/alpha/a.ts": "export const m: number = 3;",
"/home/src/workspaces/project/alpha/bin/a.d.ts": "export { };",
"/home/src/workspaces/project/beta/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
},
"references": [ { "path": "../alpha" } ]
}`),
"/home/src/workspaces/project/beta/b.ts": "import { m } from '../alpha/a'",
},
commandLineArgs: []string{"--p", "beta/tsconfig.json", "--explainFiles"},
},
{
subScenario: "issues a nice error when the input file is missing",
files: FileMap{
"/home/src/workspaces/project/alpha/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
},
"references": []
}`),
"/home/src/workspaces/project/alpha/a.ts": "export const m: number = 3;",
"/home/src/workspaces/project/beta/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
},
"references": [ { "path": "../alpha" } ]
}`),
"/home/src/workspaces/project/beta/b.ts": "import { m } from '../alpha/a'",
},
commandLineArgs: []string{"--p", "beta/tsconfig.json"},
},
{
subScenario: "issues a nice error when the input file is missing when module reference is not relative",
files: FileMap{
"/home/src/workspaces/project/alpha/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
}
}`),
"/home/src/workspaces/project/alpha/a.ts": "export const m: number = 3;",
"/home/src/workspaces/project/beta/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
"paths": {
"@alpha/*": ["../alpha/*"],
},
},
"references": [ { "path": "../alpha" } ]
}`),
"/home/src/workspaces/project/beta/b.ts": "import { m } from '@alpha/a'",
},
commandLineArgs: []string{"--p", "beta/tsconfig.json"},
},
{
subScenario: "doesnt infer the rootDir from source paths",
files: FileMap{
"/home/src/workspaces/project/alpha/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
},
"references": []
}`),
"/home/src/workspaces/project/alpha/src/a.ts": "export const m: number = 3;",
},
commandLineArgs: []string{"--p", "alpha/tsconfig.json"},
},
{
// !!! sheetal rootDir error not reported
subScenario: "errors when a file is outside the rootdir",
files: FileMap{
"/home/src/workspaces/project/alpha/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"outDir": "bin",
},
"references": []
}`),
"/home/src/workspaces/project/alpha/src/a.ts": "import * as b from '../../beta/b'",
"/home/src/workspaces/project/beta/b.ts": "export { }",
},
commandLineArgs: []string{"--p", "alpha/tsconfig.json"},
},
}
for _, c := range cases {
c.run(t, "projectReferences")
}
}
func TestTypeAcquisition(t *testing.T) {
t.Parallel()
(&tscInput{
subScenario: "parse tsconfig with typeAcquisition",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"composite": true,
"noEmit": true,
},
"typeAcquisition": {
"enable": true,
"include": ["0.d.ts", "1.d.ts"],
"exclude": ["0.js", "1.js"],
"disableFilenameBasedTypeAcquisition": true,
},
}`),
},
commandLineArgs: []string{},
}).run(t, "typeAcquisition")
}