1namespace ts.projectSystem { 2 describe("unittests:: tsserver:: forceConsistentCasingInFileNames", () => { 3 it("works when extends is specified with a case insensitive file system", () => { 4 const rootPath = "/Users/username/dev/project"; 5 const file1: File = { 6 path: `${rootPath}/index.ts`, 7 content: 'import {x} from "file2";', 8 }; 9 const file2: File = { 10 path: `${rootPath}/file2.js`, 11 content: "", 12 }; 13 const file2Dts: File = { 14 path: `${rootPath}/types/file2/index.d.ts`, 15 content: "export declare const x: string;", 16 }; 17 const tsconfigAll: File = { 18 path: `${rootPath}/tsconfig.all.json`, 19 content: JSON.stringify({ 20 compilerOptions: { 21 baseUrl: ".", 22 paths: { file2: ["./file2.js"] }, 23 typeRoots: ["./types"], 24 forceConsistentCasingInFileNames: true, 25 }, 26 }), 27 }; 28 const tsconfig: File = { 29 path: `${rootPath}/tsconfig.json`, 30 content: JSON.stringify({ extends: "./tsconfig.all.json" }), 31 }; 32 33 const host = createServerHost([file1, file2, file2Dts, libFile, tsconfig, tsconfigAll], { useCaseSensitiveFileNames: false }); 34 const session = createSession(host); 35 36 openFilesForSession([file1], session); 37 const projectService = session.getProjectService(); 38 39 checkNumberOfProjects(projectService, { configuredProjects: 1 }); 40 41 const diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); 42 assert.deepEqual(diagnostics, []); 43 }); 44 45 it("works when renaming file with different casing", () => { 46 const loggerFile: File = { 47 path: `${tscWatch.projectRoot}/Logger.ts`, 48 content: `export class logger { }` 49 }; 50 const anotherFile: File = { 51 path: `${tscWatch.projectRoot}/another.ts`, 52 content: `import { logger } from "./Logger"; new logger();` 53 }; 54 const tsconfig: File = { 55 path: `${tscWatch.projectRoot}/tsconfig.json`, 56 content: JSON.stringify({ 57 compilerOptions: { forceConsistentCasingInFileNames: true } 58 }) 59 }; 60 61 const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]); 62 const session = createSession(host, { canUseEvents: true }); 63 openFilesForSession([{ file: loggerFile, projectRootPath: tscWatch.projectRoot }], session); 64 const service = session.getProjectService(); 65 checkNumberOfProjects(service, { configuredProjects: 1 }); 66 const project = service.configuredProjects.get(tsconfig.path)!; 67 checkProjectActualFiles(project, [loggerFile.path, anotherFile.path, libFile.path, tsconfig.path]); 68 verifyGetErrRequestNoErrors({ session, host, files: [loggerFile] }); 69 70 const newLoggerPath = loggerFile.path.toLowerCase(); 71 host.renameFile(loggerFile.path, newLoggerPath); 72 closeFilesForSession([loggerFile], session); 73 openFilesForSession([{ file: newLoggerPath, content: loggerFile.content, projectRootPath: tscWatch.projectRoot }], session); 74 75 // Apply edits for rename 76 openFilesForSession([{ file: anotherFile, projectRootPath: tscWatch.projectRoot }], session); 77 session.executeCommandSeq<protocol.UpdateOpenRequest>({ 78 command: protocol.CommandTypes.UpdateOpen, 79 arguments: { 80 changedFiles: [{ 81 fileName: anotherFile.path, 82 textChanges: [{ 83 newText: "./logger", 84 ...protocolTextSpanFromSubstring( 85 anotherFile.content, 86 "./Logger" 87 ) 88 }] 89 }] 90 } 91 }); 92 93 // Check errors in both files 94 verifyGetErrRequestNoErrors({ session, host, files: [newLoggerPath, anotherFile] }); 95 }); 96 97 it("when changing module name with different casing", () => { 98 const loggerFile: File = { 99 path: `${tscWatch.projectRoot}/Logger.ts`, 100 content: `export class logger { }` 101 }; 102 const anotherFile: File = { 103 path: `${tscWatch.projectRoot}/another.ts`, 104 content: `import { logger } from "./Logger"; new logger();` 105 }; 106 const tsconfig: File = { 107 path: `${tscWatch.projectRoot}/tsconfig.json`, 108 content: JSON.stringify({ 109 compilerOptions: { forceConsistentCasingInFileNames: true } 110 }) 111 }; 112 113 const host = createServerHost([loggerFile, anotherFile, tsconfig, libFile, tsconfig]); 114 const session = createSession(host, { canUseEvents: true }); 115 openFilesForSession([{ file: anotherFile, projectRootPath: tscWatch.projectRoot }], session); 116 const service = session.getProjectService(); 117 checkNumberOfProjects(service, { configuredProjects: 1 }); 118 const project = service.configuredProjects.get(tsconfig.path)!; 119 checkProjectActualFiles(project, [loggerFile.path, anotherFile.path, libFile.path, tsconfig.path]); 120 verifyGetErrRequestNoErrors({ session, host, files: [anotherFile] }); 121 122 session.executeCommandSeq<protocol.UpdateOpenRequest>({ 123 command: protocol.CommandTypes.UpdateOpen, 124 arguments: { 125 changedFiles: [{ 126 fileName: anotherFile.path, 127 textChanges: [{ 128 newText: "./logger", 129 ...protocolTextSpanFromSubstring( 130 anotherFile.content, 131 "./Logger" 132 ) 133 }] 134 }] 135 } 136 }); 137 138 const location = protocolTextSpanFromSubstring(anotherFile.content, `"./Logger"`); 139 // Check errors in both files 140 verifyGetErrRequest({ 141 host, 142 session, 143 expected: [{ 144 file: anotherFile.path, 145 syntax: [], 146 semantic: [createDiagnostic( 147 location.start, 148 location.end, 149 { 150 message: Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, 151 args: [loggerFile.path.toLowerCase(), loggerFile.path], 152 next: [{ 153 message: Diagnostics.The_file_is_in_the_program_because_Colon, 154 next: [ 155 { message: Diagnostics.Matched_by_include_pattern_0_in_1, args: ["**/*", tsconfig.path] }, 156 { message: Diagnostics.Imported_via_0_from_file_1, args: [`"./logger"`, anotherFile.path] } 157 ] 158 }] 159 } 160 )], 161 suggestion: [] 162 }] 163 }); 164 }); 165 }); 166} 167