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