Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix comment replacement in CSS
  • Loading branch information
thecrypticace committed Aug 15, 2024
commit a71246276fad0c50cea6f806b38560aff5ebaed6
70 changes: 69 additions & 1 deletion packages/tailwindcss-language-service/src/util/doc.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type { Range } from 'vscode-languageserver'
import type { TextDocument } from 'vscode-languageserver-textdocument'
import moo from 'moo'
import { spliceChangesIntoString, StringChange } from './splice-changes-into-string'

export function getTextWithoutComments(
doc: TextDocument,
type: 'html' | 'js' | 'jsx' | 'css',
range?: Range,
): string
export function getTextWithoutComments(text: string, type: 'html' | 'js' | 'jsx' | 'css'): string

export function getTextWithoutComments(
docOrText: TextDocument | string,
type: 'html' | 'js' | 'jsx' | 'css',
Expand All @@ -20,12 +22,78 @@ export function getTextWithoutComments(
}

if (type === 'css') {
return text.replace(/\/\*.*?\*\//gs, replace)
return getCssWithoutComments(text)
}

return text.replace(/<!--.*?-->/gs, replace)
}

function getCssWithoutComments(input: string) {
const DOUBLE_QUOTE = 0x22 // "
const SINGLE_QUOTE = 0x27 // '
const BACKSLASH = 0x5c // \
const SLASH = 0x2f // /
const ASTERISK = 0x2a // *
const LINE_BREAK = 0x0a // \n

let changes: StringChange[] = []

// Collect ranges for every comment in the input.
for (let i = 0; i < input.length; ++i) {
let currentChar = input.charCodeAt(i)

if (currentChar === BACKSLASH) {
i += 1
}

// Skip over strings — they are to be left untouched
else if (currentChar === SINGLE_QUOTE || currentChar === DOUBLE_QUOTE) {
for (let j = i + 1; j < input.length; ++j) {
let peekChar = input.charCodeAt(j)

// Current character is a `\` therefore the next character is escaped.
if (peekChar === BACKSLASH) {
j += 1
}

// End of the string.
else if (peekChar === currentChar) {
i = j
break
} else if (peekChar === LINE_BREAK) {
i = j
break
}
}
} else if (currentChar === SLASH && input.charCodeAt(i + 1) === ASTERISK) {
let start = i

for (let j = i + 2; j < input.length; j++) {
let peekChar = input.charCodeAt(j)

// Current character is a `\` therefore the next character is escaped.
if (peekChar === BACKSLASH) {
j += 1
}

// End of the comment
else if (peekChar === ASTERISK && input.charCodeAt(j + 1) === SLASH) {
i = j + 1
break
}
}

changes.push({
start,
end: i + 1,
replacement: replace(input.slice(start, i + 1)),
})
}
}

return spliceChangesIntoString(input, changes)
}

function replace(match: string): string {
return match.replace(/./gs, (char) => (char === '\n' ? '\n' : ' '))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export interface StringChange {
start: number
end: number
replacement: string
}

/**
* Apply the changes to the string such that a change in the length
* of the string does not break the indexes of the subsequent changes.
*/
export function spliceChangesIntoString(str: string, changes: StringChange[]) {
// If there are no changes, return the original string
if (!changes[0]) return str

// Sort all changes in order to make it easier to apply them
changes.sort((a, b) => {
return a.end - b.end || a.start - b.start
})

// Append original string between each chunk, and then the chunk itself
// This is sort of a String Builder pattern, thus creating less memory pressure
let result = ''

let previous = changes[0]

result += str.slice(0, previous.start)
result += previous.replacement

for (let i = 1; i < changes.length; ++i) {
let change = changes[i]

result += str.slice(previous.end, change.start)
result += change.replacement

previous = change
}

// Add leftover string from last chunk to end
result += str.slice(previous.end)

return result
}