-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Excessive calls to this.toString in positionInside, positionBy and rangeBy #1847
Description
The intention of these function is to find a position within the string representation of a specific bit of CSS.
So calling this.toString() is important and inevitable.
However these functions call each other but do not pass on the string representation, causing a lot of duplicate work.
This is especially bad in rules.
For rules in particular these functions are called mostly to get positions with selectors, but the entire rule body is also converted to a string representation multiple times, this includes all declarations, nested rules, ...
None of this work is cached/memoized.
Is there anything we can do to reduce the cost of serialization in PostCSS in particular?
A simplistic change that passes on work between these functions already reduces the amount of work :
- positionInside(index) {
- let string = this.toString()
+ positionInside(index, stringRepresentation) {
+ let string = stringRepresentation || this.toString()
let column = this.source.start.column
let line = this.source.start.line
for (let i = 0; i < index; i++) {
if (string[i] === '\n') {
column = 1
line += 1
} else {
column += 1
}
}
return { line, column }
}
- positionBy(opts) {
+ positionBy(opts, stringRepresentation) {
let pos = this.source.start
if (opts.index) {
- pos = this.positionInside(opts.index)
+ pos = this.positionInside(opts.index, stringRepresentation)
} else if (opts.word) {
- let index = this.toString().indexOf(opts.word)
- if (index !== -1) pos = this.positionInside(index)
+ stringRepresentation = this.toString()
+ let index = stringRepresentation.indexOf(opts.word)
+ if (index !== -1) pos = this.positionInside(index, stringRepresentation)
}
return pos
}
rangeBy(opts) {
let start = {
line: this.source.start.line,
column: this.source.start.column
}
let end = this.source.end
? {
line: this.source.end.line,
column: this.source.end.column + 1
}
: {
line: start.line,
column: start.column + 1
}
if (opts.word) {
- let index = this.toString().indexOf(opts.word)
+ let stringRepresentation = this.toString()
+ let index = stringRepresentation.indexOf(opts.word)
if (index !== -1) {
- start = this.positionInside(index)
- end = this.positionInside(index + opts.word.length)
+ start = this.positionInside(index, stringRepresentation)
+ end = this.positionInside(index + opts.word.length, stringRepresentation)
}
} else {
if (opts.start) {
start = {
line: opts.start.line,
column: opts.start.column
}
} else if (opts.index) {
start = this.positionInside(opts.index)
}
if (opts.end) {
end = {
line: opts.end.line,
column: opts.end.column
}
} else if (opts.endIndex) {
end = this.positionInside(opts.endIndex)
} else if (opts.index) {
end = this.positionInside(opts.index + 1)
}
}
if (
end.line < start.line ||
(end.line === start.line && end.column <= start.column)
) {
end = { line: start.line, column: start.column + 1 }
}
return { start, end }
}