1namespace ts.tscWatch { 2 describe("unittests:: tsc-watch:: forceConsistentCasingInFileNames", () => { 3 const loggerFile: File = { 4 path: `${projectRoot}/logger.ts`, 5 content: `export class logger { }` 6 }; 7 const anotherFile: File = { 8 path: `${projectRoot}/another.ts`, 9 content: `import { logger } from "./logger"; new logger();` 10 }; 11 const tsconfig: File = { 12 path: `${projectRoot}/tsconfig.json`, 13 content: JSON.stringify({ 14 compilerOptions: { forceConsistentCasingInFileNames: true } 15 }) 16 }; 17 18 function verifyConsistentFileNames({ subScenario, changes }: { subScenario: string; changes: TscWatchCompileChange[]; }) { 19 verifyTscWatch({ 20 scenario: "forceConsistentCasingInFileNames", 21 subScenario, 22 commandLineArgs: ["--w", "--p", tsconfig.path], 23 sys: () => createWatchedSystem([loggerFile, anotherFile, tsconfig, libFile, tsconfig]), 24 changes 25 }); 26 } 27 28 verifyConsistentFileNames({ 29 subScenario: "when changing module name with different casing", 30 changes: [ 31 { 32 caption: "Change module name from logger to Logger", 33 change: sys => sys.writeFile(anotherFile.path, anotherFile.content.replace("./logger", "./Logger")), 34 timeouts: runQueuedTimeoutCallbacks, 35 } 36 ] 37 }); 38 39 verifyConsistentFileNames({ 40 subScenario: "when renaming file with different casing", 41 changes: [ 42 { 43 caption: "Change name of file from logger to Logger", 44 change: sys => sys.renameFile(loggerFile.path, `${projectRoot}/Logger.ts`), 45 timeouts: runQueuedTimeoutCallbacks, 46 } 47 ] 48 }); 49 50 verifyTscWatch({ 51 scenario: "forceConsistentCasingInFileNames", 52 subScenario: "when relative information file location changes", 53 commandLineArgs: ["--w", "--p", ".", "--explainFiles"], 54 sys: () => { 55 const moduleA: File = { 56 path: `${projectRoot}/moduleA.ts`, 57 content: `import a = require("./ModuleC")` 58 }; 59 const moduleB: File = { 60 path: `${projectRoot}/moduleB.ts`, 61 content: `import a = require("./moduleC")` 62 }; 63 const moduleC: File = { 64 path: `${projectRoot}/moduleC.ts`, 65 content: `export const x = 10;` 66 }; 67 const tsconfig: File = { 68 path: `${projectRoot}/tsconfig.json`, 69 content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) 70 }; 71 return createWatchedSystem([moduleA, moduleB, moduleC, libFile, tsconfig], { currentDirectory: projectRoot }); 72 }, 73 changes: [ 74 { 75 caption: "Prepend a line to moduleA", 76 change: sys => sys.prependFile(`${projectRoot}/moduleA.ts`, `// some comment 77 `), 78 timeouts: runQueuedTimeoutCallbacks, 79 } 80 ], 81 }); 82 83 verifyTscWatch({ 84 scenario: "forceConsistentCasingInFileNames", 85 subScenario: "jsxImportSource option changed", 86 commandLineArgs: ["--w", "--p", ".", "--explainFiles"], 87 sys: () => createWatchedSystem([ 88 libFile, 89 { 90 path: `${projectRoot}/node_modules/react/Jsx-runtime/index.d.ts`, 91 content: `export namespace JSX { 92 interface Element {} 93 interface IntrinsicElements { 94 div: { 95 propA?: boolean; 96 }; 97 } 98} 99export function jsx(...args: any[]): void; 100export function jsxs(...args: any[]): void; 101export const Fragment: unique symbol; 102`, 103 }, 104 { 105 path: `${projectRoot}/node_modules/react/package.json`, 106 content: JSON.stringify({ name: "react", version: "0.0.1" }) 107 }, 108 { 109 path: `${projectRoot}/index.tsx`, 110 content: `export const App = () => <div propA={true}></div>;` 111 }, 112 { 113 path: `${projectRoot}/tsconfig.json`, 114 content: JSON.stringify({ 115 compilerOptions: { jsx: "react-jsx", jsxImportSource: "react", forceConsistentCasingInFileNames: true }, 116 files: ["node_modules/react/jsx-Runtime/index.d.ts", "index.tsx"] // NB: casing does not match disk 117 }) 118 } 119 ], { currentDirectory: projectRoot }), 120 changes: emptyArray, 121 }); 122 123 function verifyWindowsStyleRoot(subScenario: string, windowsStyleRoot: string, projectRootRelative: string) { 124 verifyTscWatch({ 125 scenario: "forceConsistentCasingInFileNames", 126 subScenario, 127 commandLineArgs: ["--w", "--p", `${windowsStyleRoot}/${projectRootRelative}`, "--explainFiles"], 128 sys: () => { 129 const moduleA: File = { 130 path: `${windowsStyleRoot}/${projectRootRelative}/a.ts`, 131 content: ` 132export const a = 1; 133export const b = 2; 134` 135 }; 136 const moduleB: File = { 137 path: `${windowsStyleRoot}/${projectRootRelative}/b.ts`, 138 content: ` 139import { a } from "${windowsStyleRoot.toLocaleUpperCase()}/${projectRootRelative}/a" 140import { b } from "${windowsStyleRoot.toLocaleLowerCase()}/${projectRootRelative}/a" 141 142a;b; 143` 144 }; 145 const tsconfig: File = { 146 path: `${windowsStyleRoot}/${projectRootRelative}/tsconfig.json`, 147 content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) 148 }; 149 return createWatchedSystem([moduleA, moduleB, libFile, tsconfig], { windowsStyleRoot, useCaseSensitiveFileNames: false }); 150 }, 151 changes: [ 152 { 153 caption: "Prepend a line to moduleA", 154 change: sys => sys.prependFile(`${windowsStyleRoot}/${projectRootRelative}/a.ts`, `// some comment 155 `), 156 timeouts: runQueuedTimeoutCallbacks, 157 } 158 ], 159 }); 160 } 161 162 verifyWindowsStyleRoot("when Windows-style drive root is lowercase", "c:/", "project"); 163 verifyWindowsStyleRoot("when Windows-style drive root is uppercase", "C:/", "project"); 164 165 function verifyFileSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) { 166 verifyTscWatch({ 167 scenario: "forceConsistentCasingInFileNames", 168 subScenario, 169 commandLineArgs: ["--w", "--p", ".", "--explainFiles"], 170 sys: () => { 171 const moduleA: File = { 172 173 path: diskPath, 174 content: ` 175export const a = 1; 176export const b = 2; 177` 178 }; 179 const symlinkA: SymLink = { 180 path: `${projectRoot}/link.ts`, 181 symLink: targetPath, 182 }; 183 const moduleB: File = { 184 path: `${projectRoot}/b.ts`, 185 content: ` 186import { a } from "${importedPath}"; 187import { b } from "./link"; 188 189a;b; 190` 191 }; 192 const tsconfig: File = { 193 path: `${projectRoot}/tsconfig.json`, 194 content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } }) 195 }; 196 return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: projectRoot }); 197 }, 198 changes: [ 199 { 200 caption: "Prepend a line to moduleA", 201 change: sys => sys.prependFile(diskPath, `// some comment 202 `), 203 timeouts: runQueuedTimeoutCallbacks, 204 } 205 ], 206 }); 207 } 208 209 verifyFileSymlink("when both file symlink target and import match disk", `${projectRoot}/XY.ts`, `${projectRoot}/XY.ts`, `./XY`); 210 verifyFileSymlink("when file symlink target matches disk but import does not", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./XY`); 211 verifyFileSymlink("when import matches disk but file symlink target does not", `${projectRoot}/XY.ts`, `${projectRoot}/XY.ts`, `./Xy`); 212 verifyFileSymlink("when import and file symlink target agree but do not match disk", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./Xy`); 213 verifyFileSymlink("when import, file symlink target, and disk are all different", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./yX`); 214 215 function verifyDirSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) { 216 verifyTscWatch({ 217 scenario: "forceConsistentCasingInFileNames", 218 subScenario, 219 commandLineArgs: ["--w", "--p", ".", "--explainFiles"], 220 sys: () => { 221 const moduleA: File = { 222 223 path: `${diskPath}/a.ts`, 224 content: ` 225export const a = 1; 226export const b = 2; 227` 228 }; 229 const symlinkA: SymLink = { 230 path: `${projectRoot}/link`, 231 symLink: targetPath, 232 }; 233 const moduleB: File = { 234 path: `${projectRoot}/b.ts`, 235 content: ` 236import { a } from "${importedPath}/a"; 237import { b } from "./link/a"; 238 239a;b; 240` 241 }; 242 const tsconfig: File = { 243 path: `${projectRoot}/tsconfig.json`, 244 // Use outFile because otherwise the real and linked files will have the same output path 245 content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true, outFile: "out.js", module: "system" } }) 246 }; 247 return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: projectRoot }); 248 }, 249 changes: [ 250 { 251 caption: "Prepend a line to moduleA", 252 change: sys => sys.prependFile(`${diskPath}/a.ts`, `// some comment 253 `), 254 timeouts: runQueuedTimeoutCallbacks, 255 } 256 ], 257 }); 258 } 259 260 verifyDirSymlink("when both directory symlink target and import match disk", `${projectRoot}/XY`, `${projectRoot}/XY`, `./XY`); 261 verifyDirSymlink("when directory symlink target matches disk but import does not", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./XY`); 262 verifyDirSymlink("when import matches disk but directory symlink target does not", `${projectRoot}/XY`, `${projectRoot}/XY`, `./Xy`); 263 verifyDirSymlink("when import and directory symlink target agree but do not match disk", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./Xy`); 264 verifyDirSymlink("when import, directory symlink target, and disk are all different", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./yX`); 265 }); 266} 267