@@ -56,8 +56,10 @@ const EXECUTABLE_SCRIPT_TYPES = new Set([
5656] )
5757
5858const NEXT_SCRIPT_IMPORT = / i m p o r t \s + ( \w + ) \s * (?: , \s * \{ [ ^ } ] * \} \s * ) ? f r o m \s * [ ' " ] n e x t \/ s c r i p t [ ' " ] /
59- const TYPE_ATTRIBUTE =
60- / \b t y p e \s * = \s * (?: ' ( [ ^ ' ] * ) ' | " ( [ ^ " ] * ) " | \{ \s * (?: ' ( [ ^ ' ] * ) ' | " ( [ ^ " ] * ) " | ` ( [ ^ ` $ ] * ) ` ) \s * \} ) /
59+ /** The start of the element's own `type` prop — not a suffix like `data-type`. */
60+ const TYPE_PROP = / (?: ^ | \s ) t y p e \s * = \s * /
61+ /** A statically known `type` value right after {@link TYPE_PROP}. */
62+ const LITERAL_TYPE_VALUE = / ^ (?: ' ( [ ^ ' ] * ) ' | " ( [ ^ " ] * ) " | \{ \s * (?: ' ( [ ^ ' ] * ) ' | " ( [ ^ " ] * ) " | ` ( [ ^ ` $ ] * ) ` ) \s * \} ) /
6163
6264/** The attribute text of the JSX opening tag that starts at `start`, skipping `>` inside braces. */
6365function openingTagAttributes ( source : string , start : number ) : string {
@@ -71,17 +73,21 @@ function openingTagAttributes(source: string, start: number): string {
7173 return source . slice ( start )
7274}
7375
74- /** Line numbers of `next/script` elements in `source` whose `type` is not JavaScript. */
76+ /**
77+ * Line numbers of `next/script` elements in `source` whose `type` is not JavaScript. A `type` set
78+ * from an expression fails closed: the audit cannot prove it executable.
79+ */
7580function findDataNextScripts ( source : string ) : number [ ] {
7681 const localName = NEXT_SCRIPT_IMPORT . exec ( source ) ?. [ 1 ]
7782 if ( ! localName ) return [ ]
7883 const lines : number [ ] = [ ]
7984 for ( const match of source . matchAll ( new RegExp ( `<${ localName } \\b` , 'g' ) ) ) {
8085 const attributes = openingTagAttributes ( source , match . index + match [ 0 ] . length )
81- const typeMatch = TYPE_ATTRIBUTE . exec ( attributes )
82- if ( ! typeMatch ) continue
83- const type = typeMatch . slice ( 1 ) . find ( ( value ) => value !== undefined ) ?? ''
84- if ( EXECUTABLE_SCRIPT_TYPES . has ( type . trim ( ) . toLowerCase ( ) ) ) continue
86+ const typeProp = TYPE_PROP . exec ( attributes )
87+ if ( ! typeProp ) continue
88+ const literal = LITERAL_TYPE_VALUE . exec ( attributes . slice ( typeProp . index + typeProp [ 0 ] . length ) )
89+ const type = literal ?. slice ( 1 ) . find ( ( value ) => value !== undefined )
90+ if ( type !== undefined && EXECUTABLE_SCRIPT_TYPES . has ( type . trim ( ) . toLowerCase ( ) ) ) continue
8591 lines . push ( source . slice ( 0 , match . index ) . split ( '\n' ) . length )
8692 }
8793 return lines
@@ -110,7 +116,7 @@ for (const file of files) {
110116 if ( ! ( await source . exists ( ) ) ) continue
111117 const bytes = await source . bytes ( )
112118 if ( bytes . includes ( 0 ) ) nulOffenders . push ( file )
113- if ( file . startsWith ( 'apps/' ) && / \. [ j t ] s x $ / . test ( file ) ) {
119+ if ( file . startsWith ( 'apps/' ) && / \. (?: [ j t ] s x | m d x ) $ / . test ( file ) ) {
114120 const text = new TextDecoder ( ) . decode ( bytes )
115121 if ( ! text . includes ( 'next/script' ) ) continue
116122 for ( const line of findDataNextScripts ( text ) ) dataScriptOffenders . push ( `${ file } :${ line } ` )
0 commit comments