From 47dc4d2d453bdb7579ed4069789e9dafe0e25990 Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Tue, 7 Dec 2021 19:55:08 +0000 Subject: [PATCH] fix: trim leading newline We trim this so we don't trigger stylelint's rules which disallow empty first lines. In JS sources, an empty first line makes sense. We also have to set `beforeStart` explicitly on the root's raws until stylelint fixes stylelint/stylelint#5767. --- src/locationCorrection.ts | 20 +++++++++++++------- src/parse.ts | 20 +++++++++++++++++++- src/test/parse_test.ts | 4 ++-- src/test/postcss_test.ts | 2 +- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/locationCorrection.ts b/src/locationCorrection.ts index 7e013cd..90d6ad8 100644 --- a/src/locationCorrection.ts +++ b/src/locationCorrection.ts @@ -5,7 +5,8 @@ import {createPlaceholder} from './util.js'; const correctLocation = ( node: TaggedTemplateExpression, loc: Position, - baseIndentations: Map + baseIndentations: Map, + prefixOffsets?: {lines: number; offset: number} ): Position => { if (!node.quasi.loc || !node.quasi.range) { return loc; @@ -19,6 +20,11 @@ const correctLocation = ( let currentLine = 1; let columnOffset = nodeLoc.start.column + 1; + if (prefixOffsets) { + lineOffset += prefixOffsets.lines; + newOffset += prefixOffsets.offset; + } + for (let i = 0; i < node.quasi.expressions.length; i++) { const expr = node.quasi.expressions[i]; const previousQuasi = node.quasi.quasis[i]; @@ -72,9 +78,7 @@ const correctLocation = ( if (loc.line === currentLine) { loc.column += columnOffset; } - if (loc.line !== 1) { - loc.column += baseIndentation; - } + loc.column += baseIndentation; loc.offset = newOffset + indentationOffset; @@ -94,7 +98,7 @@ function computeBeforeAfter( ): void { if ( node.raws['before'] && - node.raws['before'].includes('\n') && + (node.raws['before'].includes('\n') || node.parent?.type === 'root') && node.source?.start ) { const raw = node.raws['before']; @@ -140,14 +144,16 @@ export function locationCorrectionWalker( node.source.start = correctLocation( expr, node.source.start, - baseIndentations + baseIndentations, + root.raws['litPrefixOffsets'] ); } if (node.source?.end) { node.source.end = correctLocation( expr, node.source.end, - baseIndentations + baseIndentations, + root.raws['litPrefixOffsets'] ); } }; diff --git a/src/parse.ts b/src/parse.ts index 1cb7d6a..0574a97 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -70,7 +70,15 @@ export const parse: Parser = ( const sourceLines = styleText.split('\n'); const baseIndentations = new Map(); const indentationPattern = new RegExp(`^[ \\t]{${baseIndentation}}`); + const emptyLinePattern = /^[ \\t\r]*$/; const deindentedLines: string[] = []; + const prefixOffsets = {lines: 0, offset: 0}; + + if (sourceLines[0] !== undefined && emptyLinePattern.test(sourceLines[0])) { + prefixOffsets.lines = 1; + prefixOffsets.offset = sourceLines[0].length + 1; + sourceLines.shift(); + } for (let i = 0; i < sourceLines.length; i++) { const sourceLine = sourceLines[i]; @@ -95,9 +103,19 @@ export const parse: Parser = ( map: false }) as Root; + root.raws['litPrefixOffsets'] = prefixOffsets; root.raws['litTemplateExpressions'] = expressionStrings; root.raws['litBaseIndentations'] = baseIndentations; - root.raws.codeBefore = sourceAsString.slice(currentOffset, startIndex); + // TODO (43081j): remove this if stylelint/stylelint#5767 ever gets fixed, + // or they drop the indentation rule. Their indentation rule depends on + // `beforeStart` existing as they unsafely try to call `endsWith` on it. + if (!root.raws['beforeStart']) { + root.raws['beforeStart'] = ''; + } + root.raws.codeBefore = sourceAsString.slice( + currentOffset, + startIndex + prefixOffsets.offset + ); root.parent = doc; // TODO (43081j): stylelint relies on this existing, really unsure why. // it could just access root.parent to get the document... diff --git a/src/test/parse_test.ts b/src/test/parse_test.ts index 6779b1c..abed0ba 100644 --- a/src/test/parse_test.ts +++ b/src/test/parse_test.ts @@ -16,7 +16,7 @@ describe('parse', () => { assert.equal(root.type, 'root'); assert.equal(rule.type, 'rule'); assert.equal(colour.type, 'decl'); - assert.equal(root.raws.codeBefore, '\n css`'); + assert.equal(root.raws.codeBefore, '\n css`\n'); assert.equal(root.parent, ast); assert.equal(root.raws.codeAfter, '`;\n '); assert.deepEqual(ast.source!.start, { @@ -73,7 +73,7 @@ describe('parse', () => { const root2 = ast.nodes[1] as Root; assert.equal(root1.type, 'root'); - assert.equal(root1.raws.codeBefore, '\n css`'); + assert.equal(root1.raws.codeBefore, '\n css`\n'); assert.equal(root1.raws.codeAfter, undefined); assert.equal(root1.parent, ast); assert.equal(root2.type, 'root'); diff --git a/src/test/postcss_test.ts b/src/test/postcss_test.ts index ccc30f9..685a63b 100644 --- a/src/test/postcss_test.ts +++ b/src/test/postcss_test.ts @@ -25,7 +25,7 @@ describe('postcss', () => { assert.equal(root.type, 'root'); assert.equal(rule.type, 'rule'); assert.equal(colour.type, 'decl'); - assert.equal(root.raws.codeBefore, '\n css`'); + assert.equal(root.raws.codeBefore, '\n css`\n'); assert.equal(root.parent, ast); assert.equal(root.raws.codeAfter, '`;\n '); assert.deepEqual(ast.source!.start, {