@@ -36,6 +36,31 @@ export interface ParseOptions {
3636 from ?: string
3737}
3838
39+ function getLineAndColumn ( input : string , position : number ) : { line : number ; column : number } {
40+ let line = 1
41+ let column = 1
42+
43+ for ( let i = 0 ; i < position && i < input . length ; i ++ ) {
44+ if ( input . charCodeAt ( i ) === LINE_BREAK ) {
45+ line ++
46+ column = 1
47+ } else {
48+ column ++
49+ }
50+ }
51+
52+ return { line, column }
53+ }
54+
55+ function formatError ( message : string , source : Source | null , position : number ) : string {
56+ if ( ! source ) {
57+ return message
58+ }
59+
60+ const { line, column } = getLineAndColumn ( source . code , position )
61+ return `${ message } at ${ source . file } :${ line } :${ column } `
62+ }
63+
3964export function parse ( input : string , opts ?: ParseOptions ) {
4065 let source : Source | null = opts ?. from ? { file : opts . from , code : input } : null
4166
@@ -138,7 +163,7 @@ export function parse(input: string, opts?: ParseOptions) {
138163
139164 // Start of a string.
140165 else if ( currentChar === SINGLE_QUOTE || currentChar === DOUBLE_QUOTE ) {
141- let end = parseString ( input , i , currentChar )
166+ let end = parseString ( input , i , currentChar , source )
142167
143168 // Adjust `buffer` to include the string.
144169 buffer += input . slice ( i , end + 1 )
@@ -192,7 +217,7 @@ export function parse(input: string, opts?: ParseOptions) {
192217
193218 // Start of a string.
194219 else if ( peekChar === SINGLE_QUOTE || peekChar === DOUBLE_QUOTE ) {
195- j = parseString ( input , j , peekChar )
220+ j = parseString ( input , j , peekChar , source )
196221 }
197222
198223 // Start of a comment.
@@ -269,7 +294,7 @@ export function parse(input: string, opts?: ParseOptions) {
269294 }
270295
271296 let declaration = parseDeclaration ( buffer , colonIdx )
272- if ( ! declaration ) throw new Error ( `Invalid custom property, expected a value` )
297+ if ( ! declaration ) throw new Error ( formatError ( `Invalid custom property, expected a value` , source , start ) )
273298
274299 if ( source ) {
275300 declaration . src = [ source , start , i ]
@@ -334,7 +359,7 @@ export function parse(input: string, opts?: ParseOptions) {
334359 let declaration = parseDeclaration ( buffer )
335360 if ( ! declaration ) {
336361 if ( buffer . length === 0 ) continue
337- throw new Error ( `Invalid declaration: \`${ buffer . trim ( ) } \`` )
362+ throw new Error ( formatError ( `Invalid declaration: \`${ buffer . trim ( ) } \`` , source , bufferStart ) )
338363 }
339364
340365 if ( source ) {
@@ -391,7 +416,7 @@ export function parse(input: string, opts?: ParseOptions) {
391416 closingBracketStack [ closingBracketStack . length - 1 ] !== ')'
392417 ) {
393418 if ( closingBracketStack === '' ) {
394- throw new Error ( 'Missing opening {' )
419+ throw new Error ( formatError ( 'Missing opening {' , source , i ) )
395420 }
396421
397422 closingBracketStack = closingBracketStack . slice ( 0 , - 1 )
@@ -453,7 +478,7 @@ export function parse(input: string, opts?: ParseOptions) {
453478 // Attach the declaration to the parent.
454479 if ( parent ) {
455480 let node = parseDeclaration ( buffer , colonIdx )
456- if ( ! node ) throw new Error ( `Invalid declaration: \`${ buffer . trim ( ) } \`` )
481+ if ( ! node ) throw new Error ( formatError ( `Invalid declaration: \`${ buffer . trim ( ) } \`` , source , bufferStart ) )
457482
458483 if ( source ) {
459484 node . src = [ source , bufferStart , i ]
@@ -492,7 +517,7 @@ export function parse(input: string, opts?: ParseOptions) {
492517 // `)`
493518 else if ( currentChar === CLOSE_PAREN ) {
494519 if ( closingBracketStack [ closingBracketStack . length - 1 ] !== ')' ) {
495- throw new Error ( 'Missing opening (' )
520+ throw new Error ( formatError ( 'Missing opening (' , source , i ) )
496521 }
497522
498523 closingBracketStack = closingBracketStack . slice ( 0 , - 1 )
@@ -534,10 +559,10 @@ export function parse(input: string, opts?: ParseOptions) {
534559 // have a leftover `parent`, then it means that we have an unterminated block.
535560 if ( closingBracketStack . length > 0 && parent ) {
536561 if ( parent . kind === 'rule' ) {
537- throw new Error ( `Missing closing } at ${ parent . selector } ` )
562+ throw new Error ( formatError ( `Missing closing } at ${ parent . selector } ` , source , input . length ) )
538563 }
539564 if ( parent . kind === 'at-rule' ) {
540- throw new Error ( `Missing closing } at ${ parent . name } ${ parent . params } ` )
565+ throw new Error ( formatError ( `Missing closing } at ${ parent . name } ${ parent . params } ` , source , input . length ) )
541566 }
542567 }
543568
@@ -594,7 +619,7 @@ function parseDeclaration(
594619 )
595620}
596621
597- function parseString ( input : string , startIdx : number , quoteChar : number ) : number {
622+ function parseString ( input : string , startIdx : number , quoteChar : number , source : Source | null = null ) : number {
598623 let peekChar : number
599624
600625 // We need to ensure that the closing quote is the same as the opening
@@ -637,7 +662,7 @@ function parseString(input: string, startIdx: number, quoteChar: number): number
637662 ( input . charCodeAt ( i + 1 ) === CARRIAGE_RETURN && input . charCodeAt ( i + 2 ) === LINE_BREAK ) )
638663 ) {
639664 throw new Error (
640- `Unterminated string: ${ input . slice ( startIdx , i + 1 ) + String . fromCharCode ( quoteChar ) } ` ,
665+ formatError ( `Unterminated string: ${ input . slice ( startIdx , i + 1 ) + String . fromCharCode ( quoteChar ) } ` , source , startIdx )
641666 )
642667 }
643668
@@ -656,7 +681,7 @@ function parseString(input: string, startIdx: number, quoteChar: number): number
656681 ( peekChar === CARRIAGE_RETURN && input . charCodeAt ( i + 1 ) === LINE_BREAK )
657682 ) {
658683 throw new Error (
659- `Unterminated string: ${ input . slice ( startIdx , i ) + String . fromCharCode ( quoteChar ) } ` ,
684+ formatError ( `Unterminated string: ${ input . slice ( startIdx , i ) + String . fromCharCode ( quoteChar ) } ` , source , startIdx )
660685 )
661686 }
662687 }
0 commit comments