mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-29 17:35:02 +08:00
Bug: 370734442 No-Presubmit: true Change-Id: I7021b801e5bacd4ed33643651a53b8db20ccf8b0 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5898986 Commit-Queue: Danil Somsikov <dsv@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org>
166 lines
6.5 KiB
JavaScript
166 lines
6.5 KiB
JavaScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
import { createScanner } from './htmlScanner';
|
|
import { findFirst } from '../utils/arrays';
|
|
import { TokenType } from '../htmlLanguageTypes';
|
|
import { isVoidElement } from '../languageFacts/fact';
|
|
var Node = /** @class */ (function () {
|
|
function Node(start, end, children, parent) {
|
|
this.start = start;
|
|
this.end = end;
|
|
this.children = children;
|
|
this.parent = parent;
|
|
this.closed = false;
|
|
}
|
|
Object.defineProperty(Node.prototype, "attributeNames", {
|
|
get: function () { return this.attributes ? Object.keys(this.attributes) : []; },
|
|
enumerable: true,
|
|
configurable: true
|
|
});
|
|
Node.prototype.isSameTag = function (tagInLowerCase) {
|
|
return this.tag && tagInLowerCase && this.tag.length === tagInLowerCase.length && this.tag.toLowerCase() === tagInLowerCase;
|
|
};
|
|
Object.defineProperty(Node.prototype, "firstChild", {
|
|
get: function () { return this.children[0]; },
|
|
enumerable: true,
|
|
configurable: true
|
|
});
|
|
Object.defineProperty(Node.prototype, "lastChild", {
|
|
get: function () { return this.children.length ? this.children[this.children.length - 1] : void 0; },
|
|
enumerable: true,
|
|
configurable: true
|
|
});
|
|
Node.prototype.findNodeBefore = function (offset) {
|
|
var idx = findFirst(this.children, function (c) { return offset <= c.start; }) - 1;
|
|
if (idx >= 0) {
|
|
var child = this.children[idx];
|
|
if (offset > child.start) {
|
|
if (offset < child.end) {
|
|
return child.findNodeBefore(offset);
|
|
}
|
|
var lastChild = child.lastChild;
|
|
if (lastChild && lastChild.end === child.end) {
|
|
return child.findNodeBefore(offset);
|
|
}
|
|
return child;
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
Node.prototype.findNodeAt = function (offset) {
|
|
var idx = findFirst(this.children, function (c) { return offset <= c.start; }) - 1;
|
|
if (idx >= 0) {
|
|
var child = this.children[idx];
|
|
if (offset > child.start && offset <= child.end) {
|
|
return child.findNodeAt(offset);
|
|
}
|
|
}
|
|
return this;
|
|
};
|
|
return Node;
|
|
}());
|
|
export { Node };
|
|
export function parse(text) {
|
|
var scanner = createScanner(text, undefined, undefined, true);
|
|
var htmlDocument = new Node(0, text.length, [], void 0);
|
|
var curr = htmlDocument;
|
|
var endTagStart = -1;
|
|
var endTagName = null;
|
|
var pendingAttribute = null;
|
|
var token = scanner.scan();
|
|
while (token !== TokenType.EOS) {
|
|
switch (token) {
|
|
case TokenType.StartTagOpen:
|
|
var child = new Node(scanner.getTokenOffset(), text.length, [], curr);
|
|
curr.children.push(child);
|
|
curr = child;
|
|
break;
|
|
case TokenType.StartTag:
|
|
curr.tag = scanner.getTokenText();
|
|
break;
|
|
case TokenType.StartTagClose:
|
|
if (curr.parent) {
|
|
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
|
|
if (scanner.getTokenLength()) {
|
|
curr.startTagEnd = scanner.getTokenEnd();
|
|
if (curr.tag && isVoidElement(curr.tag)) {
|
|
curr.closed = true;
|
|
curr = curr.parent;
|
|
}
|
|
}
|
|
else {
|
|
// pseudo close token from an incomplete start tag
|
|
curr = curr.parent;
|
|
}
|
|
}
|
|
break;
|
|
case TokenType.StartTagSelfClose:
|
|
if (curr.parent) {
|
|
curr.closed = true;
|
|
curr.end = scanner.getTokenEnd();
|
|
curr.startTagEnd = scanner.getTokenEnd();
|
|
curr = curr.parent;
|
|
}
|
|
break;
|
|
case TokenType.EndTagOpen:
|
|
endTagStart = scanner.getTokenOffset();
|
|
endTagName = null;
|
|
break;
|
|
case TokenType.EndTag:
|
|
endTagName = scanner.getTokenText().toLowerCase();
|
|
break;
|
|
case TokenType.EndTagClose:
|
|
if (endTagName) {
|
|
var node = curr;
|
|
// see if we can find a matching tag
|
|
while (!node.isSameTag(endTagName) && node.parent) {
|
|
node = node.parent;
|
|
}
|
|
if (node.parent) {
|
|
while (curr !== node) {
|
|
curr.end = endTagStart;
|
|
curr.closed = false;
|
|
curr = curr.parent;
|
|
}
|
|
curr.closed = true;
|
|
curr.endTagStart = endTagStart;
|
|
curr.end = scanner.getTokenEnd();
|
|
curr = curr.parent;
|
|
}
|
|
}
|
|
break;
|
|
case TokenType.AttributeName: {
|
|
pendingAttribute = scanner.getTokenText();
|
|
var attributes = curr.attributes;
|
|
if (!attributes) {
|
|
curr.attributes = attributes = {};
|
|
}
|
|
attributes[pendingAttribute] = null; // Support valueless attributes such as 'checked'
|
|
break;
|
|
}
|
|
case TokenType.AttributeValue: {
|
|
var value = scanner.getTokenText();
|
|
var attributes = curr.attributes;
|
|
if (attributes && pendingAttribute) {
|
|
attributes[pendingAttribute] = value;
|
|
pendingAttribute = null;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
token = scanner.scan();
|
|
}
|
|
while (curr.parent) {
|
|
curr.end = text.length;
|
|
curr.closed = false;
|
|
curr = curr.parent;
|
|
}
|
|
return {
|
|
roots: htmlDocument.children,
|
|
findNodeBefore: htmlDocument.findNodeBefore.bind(htmlDocument),
|
|
findNodeAt: htmlDocument.findNodeAt.bind(htmlDocument)
|
|
};
|
|
}
|