@@ -62,6 +62,151 @@ let parseFile = (content) => {
6262 . join ( "\n" ) ;
6363} ;
6464
65+ let splitLines = ( content ) => content . split ( "\n" ) ;
66+
67+ let fenceKind = ( line ) => {
68+ if ( line . startsWith ( "```res" ) ) {
69+ return "res" ;
70+ }
71+
72+ if ( line . startsWith ( "```js" ) || line . startsWith ( "```javascript" ) ) {
73+ return "js" ;
74+ }
75+
76+ return null ;
77+ } ;
78+
79+ let collectPreludeBlocks = ( content ) => {
80+ let lines = splitLines ( content ) ;
81+ let preludes = [ ] ;
82+
83+ for ( let i = 0 ; i < lines . length ; i ++ ) {
84+ if ( ! lines [ i ] . startsWith ( "```res prelude" ) ) {
85+ continue ;
86+ }
87+
88+ let start = i + 1 ;
89+ let end = start ;
90+ while ( end < lines . length && ! lines [ end ] . startsWith ( "```" ) ) {
91+ end ++ ;
92+ }
93+
94+ preludes . push ( {
95+ line : i + 1 ,
96+ content : lines . slice ( start , end ) . join ( "\n" ) ,
97+ } ) ;
98+
99+ i = end ;
100+ }
101+
102+ return preludes ;
103+ } ;
104+
105+ let collectCodeTabPairs = ( content ) => {
106+ let lines = splitLines ( content ) ;
107+ let pairs = [ ] ;
108+ let warnings = [ ] ;
109+ let inTargetTab = false ;
110+ let pendingRes = null ;
111+
112+ for ( let i = 0 ; i < lines . length ; i ++ ) {
113+ let line = lines [ i ] ;
114+
115+ if ( line . includes ( '<CodeTab labels={["ReScript", "JS Output"]}>' ) ) {
116+ inTargetTab = true ;
117+ pendingRes = null ;
118+ continue ;
119+ }
120+
121+ if ( inTargetTab && line . includes ( "</CodeTab>" ) ) {
122+ if ( pendingRes != null ) {
123+ warnings . push ( {
124+ line : pendingRes . line ,
125+ message : "missing paired JS Output block" ,
126+ } ) ;
127+ }
128+
129+ inTargetTab = false ;
130+ pendingRes = null ;
131+ continue ;
132+ }
133+
134+ if ( ! inTargetTab ) {
135+ continue ;
136+ }
137+
138+ let kind = fenceKind ( line ) ;
139+
140+ if ( kind === "res" ) {
141+ let start = i + 1 ;
142+ let end = start ;
143+ while ( end < lines . length && ! lines [ end ] . startsWith ( "```" ) ) {
144+ end ++ ;
145+ }
146+
147+ pendingRes = {
148+ line : i + 1 ,
149+ content : lines . slice ( start , end ) . join ( "\n" ) ,
150+ } ;
151+ i = end ;
152+ continue ;
153+ }
154+
155+ if ( kind === "js" && pendingRes != null ) {
156+ let start = i + 1 ;
157+ let end = start ;
158+ while ( end < lines . length && ! lines [ end ] . startsWith ( "```" ) ) {
159+ end ++ ;
160+ }
161+
162+ pairs . push ( {
163+ line : pendingRes . line ,
164+ res : pendingRes ,
165+ js : {
166+ content : lines . slice ( start , end ) . join ( "\n" ) ,
167+ } ,
168+ } ) ;
169+
170+ pendingRes = null ;
171+ i = end ;
172+ }
173+ }
174+
175+ return { pairs, warnings } ;
176+ } ;
177+
178+ let stripCompilerBoilerplate = ( output ) => {
179+ let normalized = output . replace (
180+ / ^ \/ \/ G e n e r a t e d b y R e S c r i p t , P L E A S E E D I T W I T H C A R E \n ' u s e s t r i c t ' ; \n \n / ,
181+ "" ,
182+ ) ;
183+
184+ return normalized . replace ( / \n \/ \* .* \* \/ \s * $ / s, "" ) . trimEnd ( ) ;
185+ } ;
186+
187+ let buildSnippetSource = ( { preludes, pair } ) => {
188+ let visiblePreludes = preludes
189+ . filter ( ( prelude ) => prelude . line < pair . line )
190+ . map ( ( prelude ) => prelude . content )
191+ . filter ( Boolean ) ;
192+
193+ return [ ...visiblePreludes , pair . res . content ] . filter ( Boolean ) . join ( "\n\n" ) ;
194+ } ;
195+
196+ let compileSnippet = ( tempRoot , source ) => {
197+ fs . writeFileSync ( path . join ( tempRoot , "src" , "_tempFile.res" ) , source ) ;
198+ child_process . execFileSync (
199+ "npm" ,
200+ [ "exec" , "rescript" , "build" , tempRoot , "--" , "--quiet" ] ,
201+ {
202+ cwd : projectRoot ,
203+ stdio : "pipe" ,
204+ } ,
205+ ) ;
206+
207+ return fs . readFileSync ( path . join ( tempRoot , "src" , "_tempFile.js" ) , "utf8" ) ;
208+ } ;
209+
65210let ensureTempProject = ( tempRoot ) => {
66211 fs . mkdirSync ( path . join ( tempRoot , "src" ) , { recursive : true } ) ;
67212 fs . writeFileSync ( path . join ( tempRoot , "rescript.json" ) , rescriptJson ) ;
@@ -87,6 +232,7 @@ export let run = ({
87232 ensureTempProject ( tempRoot ) ;
88233
89234 let success = true ;
235+ let warningCount = 0 ;
90236
91237 globSync ( "{manual,react}/**/*.mdx" , {
92238 cwd : docsRoot ,
@@ -111,10 +257,33 @@ export let run = ({
111257 ) ;
112258 } catch {
113259 success = false ;
260+ return ;
261+ }
262+
263+ let preludes = collectPreludeBlocks ( content ) ;
264+ let { pairs, warnings : malformedWarnings } = collectCodeTabPairs ( content ) ;
265+
266+ for ( let warning of malformedWarnings ) {
267+ logger . warn ( `${ file } :${ warning . line } ${ warning . message } ` ) ;
268+ warningCount ++ ;
269+ }
270+
271+ for ( let pair of pairs ) {
272+ let snippetSource = buildSnippetSource ( { preludes, pair } ) ;
273+ let compiledJs = compileSnippet ( tempRoot , snippetSource ) ;
274+ let expectedJs = stripCompilerBoilerplate ( compiledJs ) ;
275+ let currentJs = pair . js . content . trimEnd ( ) ;
276+
277+ if ( expectedJs !== currentJs ) {
278+ logger . warn (
279+ `${ file } :${ pair . line } JS Output is stale. Run scripts/test-examples.mjs --update` ,
280+ ) ;
281+ warningCount ++ ;
282+ }
114283 }
115284 } ) ;
116285
117- return { success, warningCount : 0 } ;
286+ return { success, warningCount } ;
118287} ;
119288
120289if ( process . argv [ 1 ] && path . resolve ( process . argv [ 1 ] ) === __filename ) {
0 commit comments