55
66"use strict" ;
77
8+ const ModuleDependencyWarning = require ( "../ModuleDependencyWarning" ) ;
89const Parser = require ( "../Parser" ) ;
10+ const WebpackError = require ( "../WebpackError" ) ;
911const ConstDependency = require ( "../dependencies/ConstDependency" ) ;
1012const CssExportDependency = require ( "../dependencies/CssExportDependency" ) ;
1113const CssImportDependency = require ( "../dependencies/CssImportDependency" ) ;
@@ -122,30 +124,8 @@ const CSS_MODE_IN_RULE = 1;
122124const CSS_MODE_IN_LOCAL_RULE = 2 ;
123125const CSS_MODE_AT_IMPORT_EXPECT_URL = 3 ;
124126const CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA = 4 ;
125- const CSS_MODE_AT_OTHER = 5 ;
126-
127- /**
128- * @param {number } mode current mode
129- * @returns {string } description of mode
130- */
131- const explainMode = mode => {
132- switch ( mode ) {
133- case CSS_MODE_TOP_LEVEL :
134- return "parsing top level css" ;
135- case CSS_MODE_IN_RULE :
136- return "parsing css rule content (global)" ;
137- case CSS_MODE_IN_LOCAL_RULE :
138- return "parsing css rule content (local)" ;
139- case CSS_MODE_AT_IMPORT_EXPECT_URL :
140- return "parsing @import (expecting url)" ;
141- case CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA :
142- return "parsing @import (expecting optionally layer, supports or media query)" ;
143- case CSS_MODE_AT_OTHER :
144- return "parsing at-rule" ;
145- default :
146- return "parsing css" ;
147- }
148- } ;
127+ const CSS_MODE_AT_IMPORT_INVALID = 5 ;
128+ const CSS_MODE_AT_NAMESPACE_INVALID = 6 ;
149129
150130class CssParser extends Parser {
151131 constructor ( {
@@ -159,6 +139,25 @@ class CssParser extends Parser {
159139 this . defaultMode = defaultMode ;
160140 }
161141
142+ /**
143+ * @param {ParserState } state parser state
144+ * @param {string } message warning message
145+ * @param {LocConverter } locConverter location converter
146+ * @param {number } start start offset
147+ * @param {number } end end offset
148+ */
149+ _emitWarning ( state , message , locConverter , start , end ) {
150+ const { line : sl , column : sc } = locConverter . get ( start ) ;
151+ const { line : el , column : ec } = locConverter . get ( end ) ;
152+
153+ state . current . addWarning (
154+ new ModuleDependencyWarning ( state . module , new WebpackError ( message ) , {
155+ start : { line : sl , column : sc } ,
156+ end : { line : el , column : ec }
157+ } )
158+ ) ;
159+ }
160+
162161 /**
163162 * @param {string | Buffer | PreparsedAst } source the source to parse
164163 * @param {ParserState } state the parser state
@@ -183,6 +182,8 @@ class CssParser extends Parser {
183182 let mode = CSS_MODE_TOP_LEVEL ;
184183 /** @type {number } */
185184 let modeNestingLevel = 0 ;
185+ /** @type {boolean } */
186+ let allowImportAtRule = true ;
186187 let modeData = undefined ;
187188 /** @type {string | boolean | undefined } */
188189 let singleClassSelector = undefined ;
@@ -247,10 +248,16 @@ class CssParser extends Parser {
247248 const parseExports = ( input , pos ) => {
248249 pos = walkCssTokens . eatWhitespaceAndComments ( input , pos ) ;
249250 const cc = input . charCodeAt ( pos ) ;
250- if ( cc !== CC_LEFT_CURLY )
251- throw new Error (
252- `Unexpected ${ input [ pos ] } at ${ pos } during parsing of ':export' (expected '{')`
251+ if ( cc !== CC_LEFT_CURLY ) {
252+ this . _emitWarning (
253+ state ,
254+ `Unexpected '${ input [ pos ] } ' at ${ pos } during parsing of ':export' (expected '{')` ,
255+ locConverter ,
256+ pos ,
257+ pos
253258 ) ;
259+ return pos ;
260+ }
254261 pos ++ ;
255262 pos = walkCssTokens . eatWhitespaceAndComments ( input , pos ) ;
256263 for ( ; ; ) {
@@ -262,9 +269,14 @@ class CssParser extends Parser {
262269 [ pos , name ] = eatText ( input , pos , eatExportName ) ;
263270 if ( pos === input . length ) return pos ;
264271 if ( input . charCodeAt ( pos ) !== CC_COLON ) {
265- throw new Error (
266- `Unexpected ${ input [ pos ] } at ${ pos } during parsing of export name in ':export' (expected ':')`
272+ this . _emitWarning (
273+ state ,
274+ `Unexpected '${ input [ pos ] } ' at ${ pos } during parsing of export name in ':export' (expected ':')` ,
275+ locConverter ,
276+ start ,
277+ pos
267278 ) ;
279+ return pos ;
268280 }
269281 pos ++ ;
270282 if ( pos === input . length ) return pos ;
@@ -280,9 +292,14 @@ class CssParser extends Parser {
280292 pos = walkCssTokens . eatWhitespaceAndComments ( input , pos ) ;
281293 if ( pos === input . length ) return pos ;
282294 } else if ( cc !== CC_RIGHT_CURLY ) {
283- throw new Error (
284- `Unexpected ${ input [ pos ] } at ${ pos } during parsing of export value in ':export' (expected ';' or '}')`
295+ this . _emitWarning (
296+ state ,
297+ `Unexpected '${ input [ pos ] } ' at ${ pos } during parsing of export value in ':export' (expected ';' or '}')` ,
298+ locConverter ,
299+ start ,
300+ pos
285301 ) ;
302+ return pos ;
286303 }
287304 const dep = new CssExportDependency ( name , value ) ;
288305 const { line : sl , column : sc } = locConverter . get ( start ) ;
@@ -348,14 +365,13 @@ class CssParser extends Parser {
348365 mode !== CSS_MODE_IN_RULE &&
349366 mode !== CSS_MODE_IN_LOCAL_RULE &&
350367 mode !== CSS_MODE_AT_IMPORT_EXPECT_URL &&
351- mode !== CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA
368+ mode !== CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA &&
369+ mode !== CSS_MODE_AT_IMPORT_INVALID &&
370+ mode !== CSS_MODE_AT_NAMESPACE_INVALID
352371 ) ;
353372 } ,
354- url : ( input , start , end , contentStart , contentEnd , isString ) => {
355- let value = normalizeUrl (
356- input . slice ( contentStart , contentEnd ) ,
357- isString
358- ) ;
373+ url : ( input , start , end , contentStart , contentEnd ) => {
374+ let value = normalizeUrl ( input . slice ( contentStart , contentEnd ) , false ) ;
359375 switch ( mode ) {
360376 case CSS_MODE_AT_IMPORT_EXPECT_URL : {
361377 modeData . url = value ;
@@ -367,6 +383,11 @@ class CssParser extends Parser {
367383 case CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA : {
368384 break ;
369385 }
386+ // Do not parse URLs in import between rules
387+ case CSS_MODE_AT_NAMESPACE_INVALID :
388+ case CSS_MODE_AT_IMPORT_INVALID : {
389+ break ;
390+ }
370391 default : {
371392 // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
372393 if ( value . length === 0 ) {
@@ -437,14 +458,28 @@ class CssParser extends Parser {
437458 atKeyword : ( input , start , end ) => {
438459 const name = input . slice ( start , end ) . toLowerCase ( ) ;
439460 if ( name === "@namespace" ) {
440- throw new Error ( "@namespace is not supported in bundled CSS" ) ;
441- }
442- if ( name === "@import" ) {
443- if ( mode !== CSS_MODE_TOP_LEVEL ) {
444- throw new Error (
445- `Unexpected @import at ${ start } during ${ explainMode ( mode ) } `
461+ mode = CSS_MODE_AT_NAMESPACE_INVALID ;
462+ this . _emitWarning (
463+ state ,
464+ "@namespace is not supported in bundled CSS" ,
465+ locConverter ,
466+ start ,
467+ end
468+ ) ;
469+ return end ;
470+ } else if ( name === "@import" ) {
471+ if ( ! allowImportAtRule ) {
472+ mode = CSS_MODE_AT_IMPORT_INVALID ;
473+ this . _emitWarning (
474+ state ,
475+ "Any @import rules must precede all other rules" ,
476+ locConverter ,
477+ start ,
478+ end
446479 ) ;
480+ return end ;
447481 }
482+
448483 mode = CSS_MODE_AT_IMPORT_EXPECT_URL ;
449484 modeData = {
450485 atRuleStart : start ,
@@ -454,51 +489,77 @@ class CssParser extends Parser {
454489 supports : undefined ,
455490 media : undefined
456491 } ;
457- }
458- if ( OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE . test ( name ) ) {
492+ } else if (
493+ isTopLevelLocal ( ) &&
494+ OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE . test ( name )
495+ ) {
459496 let pos = end ;
460497 pos = walkCssTokens . eatWhitespaceAndComments ( input , pos ) ;
461498 if ( pos === input . length ) return pos ;
462499 const [ newPos , name ] = eatText ( input , pos , eatKeyframes ) ;
500+ if ( newPos === input . length ) return newPos ;
501+ if ( input . charCodeAt ( newPos ) !== CC_LEFT_CURLY ) {
502+ this . _emitWarning (
503+ state ,
504+ `Unexpected '${ input [ newPos ] } ' at ${ newPos } during parsing of @keyframes (expected '{')` ,
505+ locConverter ,
506+ start ,
507+ end
508+ ) ;
509+
510+ return newPos ;
511+ }
463512 const { line : sl , column : sc } = locConverter . get ( pos ) ;
464513 const { line : el , column : ec } = locConverter . get ( newPos ) ;
465514 const dep = new CssLocalIdentifierDependency ( name , [ pos , newPos ] ) ;
466515 dep . setLoc ( sl , sc , el , ec ) ;
467516 module . addDependency ( dep ) ;
468517 pos = newPos ;
469- if ( pos === input . length ) return pos ;
470- if ( input . charCodeAt ( pos ) !== CC_LEFT_CURLY ) {
471- throw new Error (
472- `Unexpected ${ input [ pos ] } at ${ pos } during parsing of @keyframes (expected '{')`
473- ) ;
474- }
475518 mode = CSS_MODE_IN_LOCAL_RULE ;
476519 modeNestingLevel = 1 ;
477520 return pos + 1 ;
478- }
479- if ( name === "@media" || name === "@supports" ) {
521+ } else if ( name === "@media" || name === "@supports" ) {
522+ // TODO handle nested CSS syntax
480523 let pos = end ;
481524 const [ newPos ] = eatText ( input , pos , eatAtRuleNested ) ;
482525 pos = newPos ;
483526 if ( pos === input . length ) return pos ;
484527 if ( input . charCodeAt ( pos ) !== CC_LEFT_CURLY ) {
485- throw new Error (
486- `Unexpected ${ input [ pos ] } at ${ pos } during parsing of @media or @supports (expected '{')`
528+ this . _emitWarning (
529+ state ,
530+ `Unexpected ${ input [ pos ] } at ${ pos } during parsing of @media or @supports (expected '{')` ,
531+ locConverter ,
532+ start ,
533+ pos
487534 ) ;
535+ return pos ;
488536 }
489537 return pos + 1 ;
490538 }
491539 return end ;
492540 } ,
493541 semicolon : ( input , start , end ) => {
494542 switch ( mode ) {
495- case CSS_MODE_AT_IMPORT_EXPECT_URL :
496- throw new Error ( `Expected URL for @import at ${ start } ` ) ;
543+ case CSS_MODE_AT_IMPORT_EXPECT_URL : {
544+ this . _emitWarning (
545+ state ,
546+ `Expected URL for @import at ${ start } ` ,
547+ locConverter ,
548+ start ,
549+ end
550+ ) ;
551+ return end ;
552+ }
497553 case CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA : {
498554 if ( modeData . url === undefined ) {
499- throw new Error (
500- `Expected URL for @import at ${ modeData . atRuleStart } `
555+ this . _emitWarning (
556+ state ,
557+ `Expected URL for @import at ${ modeData . atRuleStart } ` ,
558+ locConverter ,
559+ modeData . atRuleStart ,
560+ modeData . lastPos
501561 ) ;
562+ return end ;
502563 }
503564 const semicolonPos = end ;
504565 end = walkCssTokens . eatWhiteLine ( input , end + 1 ) ;
@@ -547,6 +608,7 @@ class CssParser extends Parser {
547608 leftCurlyBracket : ( input , start , end ) => {
548609 switch ( mode ) {
549610 case CSS_MODE_TOP_LEVEL :
611+ allowImportAtRule = false ;
550612 mode = isTopLevelLocal ( )
551613 ? CSS_MODE_IN_LOCAL_RULE
552614 : CSS_MODE_IN_RULE ;
0 commit comments