11import recast , { type AST } from 'ember-template-recast' ;
22import fs from 'node:fs' ;
33import { fixFilename , combineRegexPatterns } from './utils.ts' ;
4- import { Transformer } from 'content-tag-utils' ;
4+ import { Preprocessor , type Parsed } from 'content-tag' ;
5+ import {
6+ getFileCoordinates ,
7+ type InnerCoordinates ,
8+ } from './get-file-coordinates.ts' ;
59
6- let fileCache = new Map < string , string > ( ) ;
10+ const fileCache = new Map < string , string > ( ) ;
11+ const fileModifiedTimes = new Map < string , number > ( ) ;
712
813const invalidTagPatterns = [
914 / ^ : / , // Don't process named blocks (:block-name)
1015] ;
11-
1216const isInvalidTag = combineRegexPatterns ( invalidTagPatterns ) ;
1317
18+ const p = new Preprocessor ( ) ;
19+ const parsedFiles = new Map < string , Parsed [ ] > ( ) ;
20+
21+ function isFileCacheValid ( filename : string ) : boolean {
22+ if ( ! fileCache . has ( filename ) || ! fileModifiedTimes . has ( filename ) ) {
23+ return false ;
24+ }
25+
26+ const stats = fs . statSync ( filename ) ;
27+ const cachedMtime = fileModifiedTimes . get ( filename ) ! ;
28+ return stats . mtimeMs === cachedMtime ;
29+ }
30+
31+ function parseFile ( filename : string , content : string ) : Parsed [ ] {
32+ if ( parsedFiles . has ( filename ) && isFileCacheValid ( filename ) ) {
33+ return parsedFiles . get ( filename ) ! ;
34+ }
35+ const parsed = p . parse ( content ) ;
36+ parsedFiles . set ( filename , parsed ) ;
37+ return parsed ;
38+ }
39+
1440function getFullFileContent ( filename : string ) : string {
15- if ( fileCache . has ( filename ) ) {
41+ if ( isFileCacheValid ( filename ) ) {
1642 return fileCache . get ( filename ) ! ;
1743 }
1844
1945 const content = fs . readFileSync ( filename , 'utf-8' ) ;
46+ const stats = fs . statSync ( filename ) ;
47+
2048 fileCache . set ( filename , content ) ;
49+ fileModifiedTimes . set ( filename , stats . mtimeMs ) ;
50+
2151 return content ;
2252}
2353
@@ -26,16 +56,16 @@ function getAllElementNodes(program: AST.Program): AST.ElementNode[] {
2656
2757 recast . traverse ( program , {
2858 ElementNode ( node : AST . ElementNode ) {
59+ if ( isInvalidTag ( node . tag ) ) {
60+ return ;
61+ }
2962 elementNodes . push ( node ) ;
3063 } ,
3164 } ) ;
3265
3366 return elementNodes ;
3467}
3568
36- let programNodesForFile = new Map < string , AST . Program [ ] > ( ) ;
37- let processedElementsForFile = new Map < string , number > ( ) ; // Track processed elements per file
38-
3969export function templatePlugin ( env : { filename : string } ) {
4070 const isProduction =
4171 process . env . NODE_ENV === 'production' || process . env . NODE_ENV === 'prod' ;
@@ -57,24 +87,15 @@ export function templatePlugin(env: { filename: string }) {
5787 }
5888
5989 const file = getFullFileContent ( env . filename ) ;
60- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
61- const t = new Transformer ( file ) ;
62- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
63- const expectedProgramCount = t . parseResults . length as number ;
64-
90+ const parsedFile = parseFile ( env . filename , file ) ;
91+ const expectedProgramCount = parsedFile . length ;
6592 const relativePath = fixFilename ( env . filename ) ;
6693
94+ let elementNodes : AST . ElementNode [ ] = [ ] ;
95+
6796 return {
6897 Program ( node : AST . Program ) {
69- programNodesForFile . set ( env . filename , [
70- ...( programNodesForFile . get ( env . filename ) || [ ] ) ,
71- node ,
72- ] ) ;
73-
74- // Initialize element counter for this file if needed
75- if ( ! processedElementsForFile . has ( env . filename ) ) {
76- processedElementsForFile . set ( env . filename , 0 ) ;
77- }
98+ elementNodes = getAllElementNodes ( node ) ;
7899 } ,
79100 ElementNode ( node : AST . ElementNode ) {
80101 if ( expectedProgramCount === 0 ) {
@@ -85,34 +106,24 @@ export function templatePlugin(env: { filename: string }) {
85106 return ;
86107 }
87108
88- const innerCoordinates = {
109+ const innerCoordinates : InnerCoordinates = {
89110 line : node . loc . startPosition . line ,
90111 column : node . loc . startPosition . column ,
91112 endColumn : node . loc . endPosition . column ,
92113 endLine : node . loc . endPosition . line ,
93114 } ;
94115
95- let programNodeIndex = - 1 ;
96-
97- const programNodes = programNodesForFile . get ( env . filename ) || [ ] ;
98-
99- programNodes . some ( ( programNode , index ) => {
100- const elementNodes = getAllElementNodes ( programNode ) ;
101-
102- if ( elementNodes . includes ( node ) ) {
103- programNodeIndex = index ;
104- return true ;
105- }
106-
107- return false ;
116+ const parsed = parsedFile . find ( ( program : Parsed ) => {
117+ // @ts -expect-error data is accessible but marked private, node.loc.module returns 'an unknown module'
118+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
119+ return program . contents === node . loc . data . source . source ;
108120 } ) ;
109121
110- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
111- const coords = t . reverseInnerCoordinatesOf (
112- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
113- t . parseResults [ programNodeIndex ] ,
114- innerCoordinates ,
115- ) ;
122+ if ( ! parsed ) {
123+ return ;
124+ }
125+
126+ const coords = getFileCoordinates ( file , parsed , innerCoordinates ) ;
116127
117128 node . attributes . push (
118129 recast . builders . attr (
@@ -121,35 +132,25 @@ export function templatePlugin(env: { filename: string }) {
121132 ) ,
122133 recast . builders . attr (
123134 'data-source-line' ,
124- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
125135 recast . builders . text ( coords . line . toString ( ) ) ,
126136 ) ,
127137 recast . builders . attr (
128138 'data-source-column' ,
129- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
130139 recast . builders . text ( ( coords . column + 1 ) . toString ( ) ) ,
131140 ) ,
132141 ) ;
133142
134- // Increment processed element count
135- const processedCount =
136- ( processedElementsForFile . get ( env . filename ) || 0 ) + 1 ;
137- processedElementsForFile . set ( env . filename , processedCount ) ;
138-
139- // Count total elements across all programs
140- const totalElements = programNodes . reduce ( ( sum , program ) => {
141- return sum + getAllElementNodes ( program ) . length ;
142- } , 0 ) ;
143-
144- // Check if we've reached the expected program count AND processed all elements
145- const currentProgramCount = programNodes . length ;
146- if (
147- currentProgramCount === expectedProgramCount &&
148- processedCount === totalElements
149- ) {
150- programNodesForFile = new Map < string , AST . Program [ ] > ( ) ;
151- processedElementsForFile = new Map < string , number > ( ) ;
152- fileCache = new Map < string , string > ( ) ;
143+ // mark element as processed by removing it from the list
144+ const index = elementNodes . indexOf ( node ) ;
145+ if ( index > - 1 ) {
146+ elementNodes . splice ( index , 1 ) ;
147+ }
148+
149+ // If all element nodes have been processed, clear the program contents
150+ // so files with multiple matching templates find the correct index
151+ if ( elementNodes . length === 0 ) {
152+ parsed . contents =
153+ '___________ember-source-lens-cleared-content___________' ;
153154 }
154155 } ,
155156 } ;
0 commit comments