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