@@ -244,4 +244,284 @@ describe('Parser Selection for Decorator Support', () => {
244244 expect ( parsingErrors . length ) . toBe ( 0 ) ;
245245 } ) ;
246246 } ) ;
247+
248+ describe ( 'Edge Case 1: TypeScript files with decorators' , ( ) => {
249+ const workspaceWithTypeScriptDecorators : string = path . join ( testDataFolder , 'workspaceWithTypeScriptDecorators' ) ;
250+
251+ it ( 'should parse .ts files with decorators using TypeScript parser' , async ( ) => {
252+ const configWithTs : ConfigObject = {
253+ disable_lwc_base_config : true ,
254+ file_extensions : {
255+ javascript : [ '.js' ] ,
256+ typescript : [ '.ts' ] ,
257+ html : [ '.html' ] ,
258+ css : [ '.css' ] ,
259+ other : [ ]
260+ } ,
261+ config_root : __dirname
262+ } ;
263+
264+ const engine : Engine = await createEngineFromPlugin ( configWithTs ) ;
265+ const workspace : Workspace = new Workspace ( 'test' , [ workspaceWithTypeScriptDecorators ] ,
266+ [ path . join ( workspaceWithTypeScriptDecorators , 'tsComponent.ts' ) ] ) ;
267+
268+ const runOptions : RunOptions = createRunOptions ( workspace ) ;
269+ const results : EngineRunResults = await engine . runRules ( [ 'no-debugger' ] , runOptions ) ;
270+
271+ // Assert: TypeScript parser should handle decorators
272+ expect ( results . violations ) . toBeDefined ( ) ;
273+ const parsingErrors = results . violations . filter ( v =>
274+ v . message . includes ( 'Parsing error' ) || v . message . includes ( 'Unexpected character' )
275+ ) ;
276+ expect ( parsingErrors . length ) . toBe ( 0 ) ;
277+ } ) ;
278+
279+ it ( 'should parse .tsx files with decorators using TypeScript parser' , async ( ) => {
280+ const configWithTsx : ConfigObject = {
281+ disable_lwc_base_config : true ,
282+ file_extensions : {
283+ javascript : [ '.js' ] ,
284+ typescript : [ '.ts' , '.tsx' ] ,
285+ html : [ '.html' ] ,
286+ css : [ '.css' ] ,
287+ other : [ ]
288+ } ,
289+ config_root : __dirname
290+ } ;
291+
292+ const engine : Engine = await createEngineFromPlugin ( configWithTsx ) ;
293+ const workspace : Workspace = new Workspace ( 'test' , [ workspaceWithTypeScriptDecorators ] ,
294+ [ path . join ( workspaceWithTypeScriptDecorators , 'tsxComponent.tsx' ) ] ) ;
295+
296+ const runOptions : RunOptions = createRunOptions ( workspace ) ;
297+ const results : EngineRunResults = await engine . runRules ( [ 'no-debugger' ] , runOptions ) ;
298+
299+ // Assert: TypeScript parser should handle decorators in JSX
300+ expect ( results . violations ) . toBeDefined ( ) ;
301+ const parsingErrors = results . violations . filter ( v =>
302+ v . message . includes ( 'Parsing error' ) || v . message . includes ( 'Unexpected character' )
303+ ) ;
304+ expect ( parsingErrors . length ) . toBe ( 0 ) ;
305+ } ) ;
306+ } ) ;
307+
308+ describe ( 'Edge Case 2: Empty file extensions' , ( ) => {
309+ it ( 'should handle empty javascript extensions gracefully' , async ( ) => {
310+ const configWithEmptyJs : ConfigObject = {
311+ disable_lwc_base_config : true ,
312+ file_extensions : {
313+ javascript : [ ] , // Empty array
314+ typescript : [ '.ts' ] ,
315+ html : [ '.html' ] ,
316+ css : [ '.css' ] ,
317+ other : [ ]
318+ } ,
319+ config_root : __dirname
320+ } ;
321+
322+ // Should not throw when creating engine
323+ const engine : Engine = await createEngineFromPlugin ( configWithEmptyJs ) ;
324+ expect ( engine ) . toBeDefined ( ) ;
325+ expect ( engine . getName ( ) ) . toBe ( 'eslint' ) ;
326+ } ) ;
327+ } ) ;
328+
329+ describe ( 'Edge Case 3: Error messages when Espree is forced on decorator files' , ( ) => {
330+ it ( 'should give clear error message when decorators fail with Espree parser' , async ( ) => {
331+ // Force Espree by using only .jsx (no .js)
332+ const configForcingEspree : ConfigObject = {
333+ disable_lwc_base_config : true ,
334+ file_extensions : {
335+ javascript : [ '.jsx' ] , // Only .jsx forces Espree
336+ typescript : [ '.ts' ] ,
337+ html : [ '.html' ] ,
338+ css : [ '.css' ] ,
339+ other : [ ]
340+ } ,
341+ config_root : __dirname
342+ } ;
343+
344+ const engine : Engine = await createEngineFromPlugin ( configForcingEspree ) ;
345+
346+ // Try to scan a .js file with decorators (will fail because only .jsx is configured)
347+ // This simulates user misconfiguration
348+ const workspace : Workspace = new Workspace ( 'test' , [ workspaceWithLwcDecorators ] ,
349+ [ path . join ( workspaceWithLwcDecorators , 'lwcComponent.js' ) ] ) ;
350+
351+ const runOptions : RunOptions = createRunOptions ( workspace ) ;
352+
353+ // The file won't be scanned because .js is not in the configured extensions
354+ // This is expected behavior - not an error, just filtered out
355+ const results : EngineRunResults = await engine . runRules ( [ 'no-debugger' ] , runOptions ) ;
356+
357+ // Should have no violations because .js files are filtered out
358+ expect ( results . violations ) . toBeDefined ( ) ;
359+ expect ( results . violations . length ) . toBe ( 0 ) ;
360+ } ) ;
361+ } ) ;
362+
363+ describe ( 'Edge Case 4: Multiple files with mixed success' , ( ) => {
364+ const workspaceWithMultipleFiles : string = path . join ( testDataFolder , 'workspaceWithMultipleFiles' ) ;
365+
366+ it ( 'should handle multiple files where some have violations' , async ( ) => {
367+ const config : ConfigObject = {
368+ disable_lwc_base_config : true ,
369+ file_extensions : {
370+ javascript : [ '.js' ] ,
371+ typescript : [ '.ts' ] ,
372+ html : [ '.html' ] ,
373+ css : [ '.css' ] ,
374+ other : [ ]
375+ } ,
376+ config_root : __dirname
377+ } ;
378+
379+ const engine : Engine = await createEngineFromPlugin ( config ) ;
380+ const workspace : Workspace = new Workspace ( 'test' , [ workspaceWithMultipleFiles ] ,
381+ [
382+ path . join ( workspaceWithMultipleFiles , 'validLwc.js' ) ,
383+ path . join ( workspaceWithMultipleFiles , 'withViolations.js' )
384+ ] ) ;
385+
386+ const runOptions : RunOptions = createRunOptions ( workspace ) ;
387+ const results : EngineRunResults = await engine . runRules ( [ 'no-debugger' , 'no-var' ] , runOptions ) ;
388+
389+ // Assert: Should parse all files, find legitimate violations in one file
390+ expect ( results . violations ) . toBeDefined ( ) ;
391+
392+ // No parsing errors - all decorators parsed successfully
393+ const parsingErrors = results . violations . filter ( v =>
394+ v . message . includes ( 'Parsing error' ) || v . message . includes ( 'Unexpected character' )
395+ ) ;
396+ expect ( parsingErrors . length ) . toBe ( 0 ) ;
397+
398+ // Should have violations from withViolations.js
399+ const debuggerViolations = results . violations . filter ( v =>
400+ v . ruleName === 'no-debugger'
401+ ) ;
402+ expect ( debuggerViolations . length ) . toBeGreaterThan ( 0 ) ;
403+
404+ const noVarViolations = results . violations . filter ( v =>
405+ v . ruleName === 'no-var'
406+ ) ;
407+ expect ( noVarViolations . length ) . toBeGreaterThan ( 0 ) ;
408+ } ) ;
409+
410+ it ( 'should successfully parse all files even when decorators are present' , async ( ) => {
411+ const config : ConfigObject = {
412+ disable_lwc_base_config : true ,
413+ disable_react_base_config : true ,
414+ file_extensions : {
415+ javascript : [ '.js' ] ,
416+ typescript : [ '.ts' ] ,
417+ html : [ '.html' ] ,
418+ css : [ '.css' ] ,
419+ other : [ ]
420+ } ,
421+ config_root : __dirname
422+ } ;
423+
424+ const engine : Engine = await createEngineFromPlugin ( config ) ;
425+ const workspace : Workspace = new Workspace ( 'test' , [ workspaceWithMultipleFiles ] ,
426+ [
427+ path . join ( workspaceWithMultipleFiles , 'validLwc.js' ) ,
428+ path . join ( workspaceWithMultipleFiles , 'withViolations.js' )
429+ ] ) ;
430+
431+ const runOptions : RunOptions = createRunOptions ( workspace ) ;
432+ const results : EngineRunResults = await engine . runRules ( [ 'no-unused-vars' ] , runOptions ) ;
433+
434+ // All files should parse without decorator errors
435+ expect ( results . violations ) . toBeDefined ( ) ;
436+ const parsingErrors = results . violations . filter ( v =>
437+ v . message . includes ( 'Parsing error' ) || v . message . includes ( 'Unexpected character' )
438+ ) ;
439+ expect ( parsingErrors . length ) . toBe ( 0 ) ;
440+ } ) ;
441+ } ) ;
442+
443+ describe ( 'Edge Case 5: Verify Babel vs Espree selection logic' , ( ) => {
444+ it ( 'should use Babel when .js is first in array' , async ( ) => {
445+ const config : ConfigObject = {
446+ disable_lwc_base_config : true ,
447+ file_extensions : {
448+ javascript : [ '.js' , '.jsx' , '.mjs' ] , // .js first
449+ typescript : [ '.ts' ] ,
450+ html : [ '.html' ] ,
451+ css : [ '.css' ] ,
452+ other : [ ]
453+ } ,
454+ config_root : __dirname
455+ } ;
456+
457+ const engine : Engine = await createEngineFromPlugin ( config ) ;
458+ const workspace : Workspace = new Workspace ( 'test' , [ workspaceWithLwcDecorators ] ,
459+ [ path . join ( workspaceWithLwcDecorators , 'lwcComponent.js' ) ] ) ;
460+
461+ const runOptions : RunOptions = createRunOptions ( workspace ) ;
462+ const results : EngineRunResults = await engine . runRules ( [ 'no-debugger' ] , runOptions ) ;
463+
464+ // Should use Babel (decorators work)
465+ const parsingErrors = results . violations . filter ( v =>
466+ v . message . includes ( 'Parsing error' ) || v . message . includes ( 'Unexpected character' )
467+ ) ;
468+ expect ( parsingErrors . length ) . toBe ( 0 ) ;
469+ } ) ;
470+
471+ it ( 'should use Babel when .js is last in array' , async ( ) => {
472+ const config : ConfigObject = {
473+ disable_lwc_base_config : true ,
474+ file_extensions : {
475+ javascript : [ '.jsx' , '.mjs' , '.js' ] , // .js last
476+ typescript : [ '.ts' ] ,
477+ html : [ '.html' ] ,
478+ css : [ '.css' ] ,
479+ other : [ ]
480+ } ,
481+ config_root : __dirname
482+ } ;
483+
484+ const engine : Engine = await createEngineFromPlugin ( config ) ;
485+ const workspace : Workspace = new Workspace ( 'test' , [ workspaceWithLwcDecorators ] ,
486+ [ path . join ( workspaceWithLwcDecorators , 'lwcComponent.js' ) ] ) ;
487+
488+ const runOptions : RunOptions = createRunOptions ( workspace ) ;
489+ const results : EngineRunResults = await engine . runRules ( [ 'no-debugger' ] , runOptions ) ;
490+
491+ // Should still use Babel (includes() checks entire array)
492+ const parsingErrors = results . violations . filter ( v =>
493+ v . message . includes ( 'Parsing error' ) || v . message . includes ( 'Unexpected character' )
494+ ) ;
495+ expect ( parsingErrors . length ) . toBe ( 0 ) ;
496+ } ) ;
497+
498+ it ( 'should use Espree when .js is NOT in array' , async ( ) => {
499+ const config : ConfigObject = {
500+ disable_lwc_base_config : true ,
501+ file_extensions : {
502+ javascript : [ '.jsx' , '.mjs' , '.cjs' ] , // No .js
503+ typescript : [ '.ts' ] ,
504+ html : [ '.html' ] ,
505+ css : [ '.css' ] ,
506+ other : [ ]
507+ } ,
508+ config_root : __dirname
509+ } ;
510+
511+ const engine : Engine = await createEngineFromPlugin ( config ) ;
512+
513+ // Scan a .jsx file (should use Espree successfully)
514+ const workspace : Workspace = new Workspace ( 'test' , [ workspaceWithJsxOnly ] ,
515+ [ path . join ( workspaceWithJsxOnly , 'ReactComponent.jsx' ) ] ) ;
516+
517+ const runOptions : RunOptions = createRunOptions ( workspace ) ;
518+ const results : EngineRunResults = await engine . runRules ( [ 'no-debugger' ] , runOptions ) ;
519+
520+ // Espree should handle .jsx fine (no decorators in React files)
521+ const parsingErrors = results . violations . filter ( v =>
522+ v . message . includes ( 'Parsing error' )
523+ ) ;
524+ expect ( parsingErrors . length ) . toBe ( 0 ) ;
525+ } ) ;
526+ } ) ;
247527} ) ;
0 commit comments