mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-10 21:54:47 +08:00
The entrypoint of `formatter_worker.ts` will now handle the Worker message handling, rather than its implementation. This allows us to treat the `formatter_worker` devtools_module as purely JavaScript implementation. R=aerotwist@chromium.org Bug: 1011811 Change-Id: I02fccb5b255af55cde8622f04a2ca905d765bccc Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2410235 Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org> Reviewed-by: Paul Lewis <aerotwist@chromium.org>
200 lines
7.2 KiB
JavaScript
200 lines
7.2 KiB
JavaScript
// Copyright 2016 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import {createTokenizer} from './FormatterWorker.js';
|
|
|
|
export const CSSParserStates = {
|
|
Initial: 'Initial',
|
|
Selector: 'Selector',
|
|
Style: 'Style',
|
|
PropertyName: 'PropertyName',
|
|
PropertyValue: 'PropertyValue',
|
|
AtRule: 'AtRule'
|
|
};
|
|
|
|
/** @typedef {*} */
|
|
let Rule; // eslint-disable-line no-unused-vars
|
|
|
|
/**
|
|
* @typedef {{ chunk: Array.<!Rule>, isLastChunk: boolean }} */
|
|
let Chunk; // eslint-disable-line no-unused-vars
|
|
|
|
/**
|
|
* @param {string} text
|
|
* @param {function({ chunk: !Array.<!Rule>, isLastChunk:boolean}):void} chunkCallback
|
|
*/
|
|
export function parseCSS(text, chunkCallback) {
|
|
const chunkSize = 100000; // characters per data chunk
|
|
const lines = text.split('\n');
|
|
/** @type {!Array.<!Rule>} */
|
|
let rules = [];
|
|
let processedChunkCharacters = 0;
|
|
|
|
let state = CSSParserStates.Initial;
|
|
/** @type {!Rule} */
|
|
let rule;
|
|
/** @type {*} */
|
|
let property;
|
|
const UndefTokenType = new Set();
|
|
|
|
/** @type {!Array.<!Rule>} */
|
|
let disabledRules = [];
|
|
|
|
/**
|
|
*
|
|
* @param {!Chunk} chunk
|
|
*/
|
|
function disabledRulesCallback(chunk) {
|
|
disabledRules = disabledRules.concat(chunk.chunk);
|
|
}
|
|
|
|
/**
|
|
* @param {string} tokenValue
|
|
* @param {?string} tokenTypes
|
|
* @param {number} column
|
|
* @param {number} newColumn
|
|
*/
|
|
function processToken(tokenValue, tokenTypes, column, newColumn) {
|
|
const tokenType = tokenTypes ? new Set(tokenTypes.split(' ')) : UndefTokenType;
|
|
switch (state) {
|
|
case CSSParserStates.Initial:
|
|
if (tokenType.has('qualifier') || tokenType.has('builtin') || tokenType.has('tag')) {
|
|
rule = {
|
|
selectorText: tokenValue,
|
|
lineNumber: lineNumber,
|
|
columnNumber: column,
|
|
properties: [],
|
|
};
|
|
state = CSSParserStates.Selector;
|
|
} else if (tokenType.has('def')) {
|
|
rule = {
|
|
atRule: tokenValue,
|
|
lineNumber: lineNumber,
|
|
columnNumber: column,
|
|
};
|
|
state = CSSParserStates.AtRule;
|
|
}
|
|
break;
|
|
case CSSParserStates.Selector:
|
|
if (tokenValue === '{' && tokenType === UndefTokenType) {
|
|
rule.selectorText = rule.selectorText.trim();
|
|
rule.styleRange = createRange(lineNumber, newColumn);
|
|
state = CSSParserStates.Style;
|
|
} else {
|
|
rule.selectorText += tokenValue;
|
|
}
|
|
break;
|
|
case CSSParserStates.AtRule:
|
|
if ((tokenValue === ';' || tokenValue === '{') && tokenType === UndefTokenType) {
|
|
rule.atRule = rule.atRule.trim();
|
|
rules.push(rule);
|
|
state = CSSParserStates.Initial;
|
|
} else {
|
|
rule.atRule += tokenValue;
|
|
}
|
|
break;
|
|
case CSSParserStates.Style:
|
|
if (tokenType.has('meta') || tokenType.has('property')) {
|
|
property = {
|
|
name: tokenValue,
|
|
value: '',
|
|
range: createRange(lineNumber, column),
|
|
nameRange: createRange(lineNumber, column)
|
|
};
|
|
state = CSSParserStates.PropertyName;
|
|
} else if (tokenValue === '}' && tokenType === UndefTokenType) {
|
|
rule.styleRange.endLine = lineNumber;
|
|
rule.styleRange.endColumn = column;
|
|
rules.push(rule);
|
|
state = CSSParserStates.Initial;
|
|
} else if (tokenType.has('comment')) {
|
|
// The |processToken| is called per-line, so no token spans more than one line.
|
|
// Support only a one-line comments.
|
|
if (tokenValue.substring(0, 2) !== '/*' || tokenValue.substring(tokenValue.length - 2) !== '*/') {
|
|
break;
|
|
}
|
|
const uncommentedText = tokenValue.substring(2, tokenValue.length - 2);
|
|
const fakeRule = 'a{\n' + uncommentedText + '}';
|
|
disabledRules = [];
|
|
parseCSS(fakeRule, disabledRulesCallback);
|
|
if (disabledRules.length === 1 && disabledRules[0].properties.length === 1) {
|
|
const disabledProperty = disabledRules[0].properties[0];
|
|
disabledProperty.disabled = true;
|
|
disabledProperty.range = createRange(lineNumber, column);
|
|
disabledProperty.range.endColumn = newColumn;
|
|
const lineOffset = lineNumber - 1;
|
|
const columnOffset = column + 2;
|
|
disabledProperty.nameRange.startLine += lineOffset;
|
|
disabledProperty.nameRange.startColumn += columnOffset;
|
|
disabledProperty.nameRange.endLine += lineOffset;
|
|
disabledProperty.nameRange.endColumn += columnOffset;
|
|
disabledProperty.valueRange.startLine += lineOffset;
|
|
disabledProperty.valueRange.startColumn += columnOffset;
|
|
disabledProperty.valueRange.endLine += lineOffset;
|
|
disabledProperty.valueRange.endColumn += columnOffset;
|
|
rule.properties.push(disabledProperty);
|
|
}
|
|
}
|
|
break;
|
|
case CSSParserStates.PropertyName:
|
|
if (tokenValue === ':' && tokenType === UndefTokenType) {
|
|
property.name = property.name;
|
|
property.nameRange.endLine = lineNumber;
|
|
property.nameRange.endColumn = column;
|
|
property.valueRange = createRange(lineNumber, newColumn);
|
|
state = CSSParserStates.PropertyValue;
|
|
} else if (tokenType.has('property')) {
|
|
property.name += tokenValue;
|
|
}
|
|
break;
|
|
case CSSParserStates.PropertyValue:
|
|
if ((tokenValue === ';' || tokenValue === '}') && tokenType === UndefTokenType) {
|
|
property.value = property.value;
|
|
property.valueRange.endLine = lineNumber;
|
|
property.valueRange.endColumn = column;
|
|
property.range.endLine = lineNumber;
|
|
property.range.endColumn = tokenValue === ';' ? newColumn : column;
|
|
rule.properties.push(property);
|
|
if (tokenValue === '}') {
|
|
rule.styleRange.endLine = lineNumber;
|
|
rule.styleRange.endColumn = column;
|
|
rules.push(rule);
|
|
state = CSSParserStates.Initial;
|
|
} else {
|
|
state = CSSParserStates.Style;
|
|
}
|
|
} else if (!tokenType.has('comment')) {
|
|
property.value += tokenValue;
|
|
}
|
|
break;
|
|
default:
|
|
console.assert(false, 'Unknown CSS parser state.');
|
|
}
|
|
processedChunkCharacters += newColumn - column;
|
|
if (processedChunkCharacters > chunkSize) {
|
|
chunkCallback({chunk: rules, isLastChunk: false});
|
|
rules = [];
|
|
processedChunkCharacters = 0;
|
|
}
|
|
}
|
|
const tokenizer = createTokenizer('text/css');
|
|
/** @type {number} */
|
|
let lineNumber;
|
|
for (lineNumber = 0; lineNumber < lines.length; ++lineNumber) {
|
|
const line = lines[lineNumber];
|
|
tokenizer(line, processToken);
|
|
processToken('\n', null, line.length, line.length + 1);
|
|
}
|
|
chunkCallback({chunk: rules, isLastChunk: true});
|
|
|
|
/**
|
|
* @param {number} lineNumber
|
|
* @param {number} columnNumber
|
|
* @return {!{startLine: number, startColumn: number, endLine: number, endColumn: number}}
|
|
*/
|
|
function createRange(lineNumber, columnNumber) {
|
|
return {startLine: lineNumber, startColumn: columnNumber, endLine: lineNumber, endColumn: columnNumber};
|
|
}
|
|
}
|