Mirror: TypeScript LSP plugin that finds GraphQL documents in your code and provides diagnostics, auto-complete and hover-information.
at main 13 kB view raw
1import { expect, afterAll, beforeAll, it, describe } from 'vitest'; 2import { TSServer } from './server'; 3import path from 'node:path'; 4import fs from 'node:fs'; 5import url from 'node:url'; 6import ts from 'typescript/lib/tsserverlibrary'; 7 8const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 9 10const projectPath = path.resolve(__dirname, 'fixture-project-unused-fields'); 11describe('unused fields', () => { 12 const outfileDestructuringFromStart = path.join( 13 projectPath, 14 'immediate-destructuring.tsx' 15 ); 16 const outfileDestructuring = path.join(projectPath, 'destructuring.tsx'); 17 const outfileBail = path.join(projectPath, 'bail.tsx'); 18 const outfileFragmentDestructuring = path.join( 19 projectPath, 20 'fragment-destructuring.tsx' 21 ); 22 const outfileFragment = path.join(projectPath, 'fragment.tsx'); 23 const outfilePropAccess = path.join(projectPath, 'property-access.tsx'); 24 const outfileChainedUsage = path.join(projectPath, 'chained-usage.ts'); 25 26 let server: TSServer; 27 beforeAll(async () => { 28 server = new TSServer(projectPath, { debugLog: false }); 29 30 server.sendCommand('open', { 31 file: outfileDestructuring, 32 fileContent: '// empty', 33 scriptKindName: 'TS', 34 } satisfies ts.server.protocol.OpenRequestArgs); 35 server.sendCommand('open', { 36 file: outfileBail, 37 fileContent: '// empty', 38 scriptKindName: 'TS', 39 } satisfies ts.server.protocol.OpenRequestArgs); 40 server.sendCommand('open', { 41 file: outfileFragment, 42 fileContent: '// empty', 43 scriptKindName: 'TS', 44 } satisfies ts.server.protocol.OpenRequestArgs); 45 server.sendCommand('open', { 46 file: outfilePropAccess, 47 fileContent: '// empty', 48 scriptKindName: 'TS', 49 } satisfies ts.server.protocol.OpenRequestArgs); 50 server.sendCommand('open', { 51 file: outfileFragmentDestructuring, 52 fileContent: '// empty', 53 scriptKindName: 'TS', 54 } satisfies ts.server.protocol.OpenRequestArgs); 55 server.sendCommand('open', { 56 file: outfileDestructuringFromStart, 57 fileContent: '// empty', 58 scriptKindName: 'TS', 59 } satisfies ts.server.protocol.OpenRequestArgs); 60 server.sendCommand('open', { 61 file: outfileChainedUsage, 62 fileContent: '// empty', 63 scriptKindName: 'TS', 64 } satisfies ts.server.protocol.OpenRequestArgs); 65 66 server.sendCommand('updateOpen', { 67 openFiles: [ 68 { 69 file: outfileDestructuring, 70 fileContent: fs.readFileSync( 71 path.join(projectPath, 'fixtures/destructuring.tsx'), 72 'utf-8' 73 ), 74 }, 75 { 76 file: outfileBail, 77 fileContent: fs.readFileSync( 78 path.join(projectPath, 'fixtures/bail.tsx'), 79 'utf-8' 80 ), 81 }, 82 { 83 file: outfileFragment, 84 fileContent: fs.readFileSync( 85 path.join(projectPath, 'fixtures/fragment.tsx'), 86 'utf-8' 87 ), 88 }, 89 { 90 file: outfilePropAccess, 91 fileContent: fs.readFileSync( 92 path.join(projectPath, 'fixtures/property-access.tsx'), 93 'utf-8' 94 ), 95 }, 96 { 97 file: outfileDestructuringFromStart, 98 fileContent: fs.readFileSync( 99 path.join(projectPath, 'fixtures/immediate-destructuring.tsx'), 100 'utf-8' 101 ), 102 }, 103 { 104 file: outfileFragmentDestructuring, 105 fileContent: fs.readFileSync( 106 path.join(projectPath, 'fixtures/fragment-destructuring.tsx'), 107 'utf-8' 108 ), 109 }, 110 { 111 file: outfileChainedUsage, 112 fileContent: fs.readFileSync( 113 path.join(projectPath, 'fixtures/chained-usage.ts'), 114 'utf-8' 115 ), 116 }, 117 ], 118 } satisfies ts.server.protocol.UpdateOpenRequestArgs); 119 120 server.sendCommand('saveto', { 121 file: outfileDestructuring, 122 tmpfile: outfileDestructuring, 123 } satisfies ts.server.protocol.SavetoRequestArgs); 124 server.sendCommand('saveto', { 125 file: outfileFragment, 126 tmpfile: outfileFragment, 127 } satisfies ts.server.protocol.SavetoRequestArgs); 128 server.sendCommand('saveto', { 129 file: outfilePropAccess, 130 tmpfile: outfilePropAccess, 131 } satisfies ts.server.protocol.SavetoRequestArgs); 132 server.sendCommand('saveto', { 133 file: outfileFragmentDestructuring, 134 tmpfile: outfileFragmentDestructuring, 135 } satisfies ts.server.protocol.SavetoRequestArgs); 136 server.sendCommand('saveto', { 137 file: outfileDestructuringFromStart, 138 tmpfile: outfileDestructuringFromStart, 139 } satisfies ts.server.protocol.SavetoRequestArgs); 140 server.sendCommand('saveto', { 141 file: outfileBail, 142 tmpfile: outfileBail, 143 } satisfies ts.server.protocol.SavetoRequestArgs); 144 server.sendCommand('saveto', { 145 file: outfileChainedUsage, 146 tmpfile: outfileChainedUsage, 147 } satisfies ts.server.protocol.SavetoRequestArgs); 148 }); 149 150 afterAll(() => { 151 try { 152 fs.unlinkSync(outfileDestructuring); 153 fs.unlinkSync(outfileFragment); 154 fs.unlinkSync(outfilePropAccess); 155 fs.unlinkSync(outfileFragmentDestructuring); 156 fs.unlinkSync(outfileDestructuringFromStart); 157 fs.unlinkSync(outfileBail); 158 fs.unlinkSync(outfileChainedUsage); 159 } catch {} 160 }); 161 162 it('gives unused fields with fragments', async () => { 163 await server.waitForResponse( 164 e => 165 e.type === 'event' && 166 e.event === 'semanticDiag' && 167 e.body?.file === outfileFragment 168 ); 169 const res = server.responses.filter( 170 resp => 171 resp.type === 'event' && 172 resp.event === 'semanticDiag' && 173 resp.body?.file === outfileFragment 174 ); 175 expect(res[0].body.diagnostics).toMatchInlineSnapshot(` 176 [ 177 { 178 "category": "warning", 179 "code": 52005, 180 "end": { 181 "line": 9, 182 "offset": 11, 183 }, 184 "start": { 185 "line": 9, 186 "offset": 7, 187 }, 188 "text": "Field(s) 'attacks.fast.damage', 'attacks.fast.name' are not used.", 189 }, 190 ] 191 `); 192 }, 30000); 193 194 it('gives unused fields with fragments destructuring', async () => { 195 await server.waitForResponse( 196 e => 197 e.type === 'event' && 198 e.event === 'semanticDiag' && 199 e.body?.file === outfileFragmentDestructuring 200 ); 201 const res = server.responses.filter( 202 resp => 203 resp.type === 'event' && 204 resp.event === 'semanticDiag' && 205 resp.body?.file === outfileFragmentDestructuring 206 ); 207 expect(res[0].body.diagnostics).toMatchInlineSnapshot(` 208 [ 209 { 210 "category": "warning", 211 "code": 52005, 212 "end": { 213 "line": 9, 214 "offset": 11, 215 }, 216 "start": { 217 "line": 9, 218 "offset": 7, 219 }, 220 "text": "Field(s) 'attacks.fast.damage', 'attacks.fast.name' are not used.", 221 }, 222 ] 223 `); 224 }, 30000); 225 226 it('gives semantc diagnostics with property access', async () => { 227 await server.waitForResponse( 228 e => 229 e.type === 'event' && 230 e.event === 'semanticDiag' && 231 e.body?.file === outfilePropAccess 232 ); 233 const res = server.responses.filter( 234 resp => 235 resp.type === 'event' && 236 resp.event === 'semanticDiag' && 237 resp.body?.file === outfilePropAccess 238 ); 239 expect(res[0].body.diagnostics).toMatchInlineSnapshot(` 240 [ 241 { 242 "category": "warning", 243 "code": 52005, 244 "end": { 245 "line": 9, 246 "offset": 12, 247 }, 248 "start": { 249 "line": 9, 250 "offset": 5, 251 }, 252 "text": "Field(s) 'pokemon.fleeRate' are not used.", 253 }, 254 { 255 "category": "warning", 256 "code": 52005, 257 "end": { 258 "line": 14, 259 "offset": 16, 260 }, 261 "start": { 262 "line": 14, 263 "offset": 9, 264 }, 265 "text": "Field(s) 'pokemon.attacks.special.damage' are not used.", 266 }, 267 { 268 "category": "warning", 269 "code": 52005, 270 "end": { 271 "line": 19, 272 "offset": 13, 273 }, 274 "start": { 275 "line": 19, 276 "offset": 7, 277 }, 278 "text": "Field(s) 'pokemon.weight.minimum', 'pokemon.weight.maximum' are not used.", 279 }, 280 { 281 "category": "error", 282 "code": 2578, 283 "end": { 284 "line": 3, 285 "offset": 20, 286 }, 287 "start": { 288 "line": 3, 289 "offset": 1, 290 }, 291 "text": "Unused '@ts-expect-error' directive.", 292 }, 293 ] 294 `); 295 }, 30000); 296 297 it('gives unused fields with destructuring', async () => { 298 const res = server.responses.filter( 299 resp => 300 resp.type === 'event' && 301 resp.event === 'semanticDiag' && 302 resp.body?.file === outfileDestructuring 303 ); 304 expect(res[0].body.diagnostics).toMatchInlineSnapshot(` 305 [ 306 { 307 "category": "warning", 308 "code": 52005, 309 "end": { 310 "line": 14, 311 "offset": 16, 312 }, 313 "start": { 314 "line": 14, 315 "offset": 9, 316 }, 317 "text": "Field(s) 'pokemon.attacks.special.name', 'pokemon.attacks.special.damage' are not used.", 318 }, 319 { 320 "category": "warning", 321 "code": 52005, 322 "end": { 323 "line": 9, 324 "offset": 12, 325 }, 326 "start": { 327 "line": 9, 328 "offset": 5, 329 }, 330 "text": "Field(s) 'pokemon.name' are not used.", 331 }, 332 { 333 "category": "error", 334 "code": 2578, 335 "end": { 336 "line": 3, 337 "offset": 20, 338 }, 339 "start": { 340 "line": 3, 341 "offset": 1, 342 }, 343 "text": "Unused '@ts-expect-error' directive.", 344 }, 345 ] 346 `); 347 }, 30000); 348 349 it('gives unused fields with immedaite destructuring', async () => { 350 const res = server.responses.filter( 351 resp => 352 resp.type === 'event' && 353 resp.event === 'semanticDiag' && 354 resp.body?.file === outfileDestructuringFromStart 355 ); 356 expect(res[0].body.diagnostics).toMatchInlineSnapshot(` 357 [ 358 { 359 "category": "warning", 360 "code": 52005, 361 "end": { 362 "line": 14, 363 "offset": 16, 364 }, 365 "start": { 366 "line": 14, 367 "offset": 9, 368 }, 369 "text": "Field(s) 'pokemon.attacks.special.name', 'pokemon.attacks.special.damage' are not used.", 370 }, 371 { 372 "category": "warning", 373 "code": 52005, 374 "end": { 375 "line": 9, 376 "offset": 12, 377 }, 378 "start": { 379 "line": 9, 380 "offset": 5, 381 }, 382 "text": "Field(s) 'pokemon.name' are not used.", 383 }, 384 { 385 "category": "error", 386 "code": 2578, 387 "end": { 388 "line": 3, 389 "offset": 20, 390 }, 391 "start": { 392 "line": 3, 393 "offset": 1, 394 }, 395 "text": "Unused '@ts-expect-error' directive.", 396 }, 397 ] 398 `); 399 }, 30000); 400 401 it('Bails unused fields when memo func is used', async () => { 402 const res = server.responses.filter( 403 resp => 404 resp.type === 'event' && 405 resp.event === 'semanticDiag' && 406 resp.body?.file === outfileBail 407 ); 408 expect(res[0].body.diagnostics).toMatchInlineSnapshot(` 409 [ 410 { 411 "category": "error", 412 "code": 2578, 413 "end": { 414 "line": 4, 415 "offset": 20, 416 }, 417 "start": { 418 "line": 4, 419 "offset": 1, 420 }, 421 "text": "Unused '@ts-expect-error' directive.", 422 }, 423 ] 424 `); 425 }, 30000); 426 427 it('Finds field usage in chained call-expressions', async () => { 428 const res = server.responses.filter( 429 resp => 430 resp.type === 'event' && 431 resp.event === 'semanticDiag' && 432 resp.body?.file === outfileChainedUsage 433 ); 434 expect(res[0].body.diagnostics[0]).toEqual({ 435 category: 'warning', 436 code: 52005, 437 end: { 438 line: 8, 439 offset: 15, 440 }, 441 start: { 442 line: 8, 443 offset: 7, 444 }, 445 text: "Field(s) 'pokemons.fleeRate' are not used.", 446 }); 447 }, 30000); 448});