mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-11-06 05:09:20 +08:00
Not every single ts_library invocation has (and should have) access to the ESTree types. Therefore, re-export these types via Acorn, which is the only user of these types. This also improves the build performance, as we are no longer rebuilding all of DevTools when these types change. DISABLE_THIRD_PARTY_CHECK=Tsc cleanup R=szuend@chromium.org Bug: 1209844 Change-Id: Ib182ba4f7877d26eea844ac75180542ce2bac44a Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2900444 Auto-Submit: Tim van der Lippe <tvanderlippe@chromium.org> Commit-Queue: Simon Zünd <szuend@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org>
156 lines
5.8 KiB
TypeScript
156 lines
5.8 KiB
TypeScript
// 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.
|
|
|
|
/* eslint-disable rulesdir/no_underscored_properties */
|
|
|
|
import * as Platform from '../../core/platform/platform.js';
|
|
import * as TextUtils from '../../models/text_utils/text_utils.js';
|
|
import * as AcornLoose from '../../third_party/acorn-loose/acorn-loose.js';
|
|
import * as Acorn from '../../third_party/acorn/acorn.js';
|
|
|
|
import {ECMA_VERSION} from './AcornTokenizer.js';
|
|
import {ESTreeWalker} from './ESTreeWalker.js';
|
|
import type {ChunkCallback} from './FormatterWorker.js';
|
|
|
|
export interface Item {
|
|
title: string;
|
|
subtitle?: string;
|
|
line: number;
|
|
column: number;
|
|
}
|
|
|
|
export function javaScriptOutline(content: string, chunkCallback: ChunkCallback): void {
|
|
const chunkSize = 100000;
|
|
let chunk: Item[] = [];
|
|
let lastReportedOffset = 0;
|
|
|
|
let ast;
|
|
try {
|
|
ast = Acorn.parse(content, {ecmaVersion: ECMA_VERSION, ranges: false});
|
|
} catch (e) {
|
|
ast = AcornLoose.AcornLoose.parse(content, {ecmaVersion: ECMA_VERSION, ranges: false});
|
|
}
|
|
|
|
const contentLineEndings = Platform.StringUtilities.findLineEndingIndexes(content);
|
|
const textCursor = new TextUtils.TextCursor.TextCursor(contentLineEndings);
|
|
const walker = new ESTreeWalker(beforeVisit);
|
|
|
|
// @ts-ignore Technically, the acorn Node type is a subclass of Acorn.ESTree.Node.
|
|
// However, the acorn package currently exports its type without specifying
|
|
// this relationship. So while this is allowed on runtime, we can't properly
|
|
// typecheck it.
|
|
walker.walk(ast);
|
|
|
|
chunkCallback({chunk, isLastChunk: true});
|
|
|
|
function beforeVisit(node: Acorn.ESTree.Node): undefined {
|
|
if (node.type === 'ClassDeclaration') {
|
|
reportClass((node.id as Acorn.ESTree.Node));
|
|
} else if (node.type === 'VariableDeclarator' && node.init && isClassNode(node.init)) {
|
|
reportClass((node.id as Acorn.ESTree.Node));
|
|
} else if (node.type === 'AssignmentExpression' && isNameNode(node.left) && isClassNode(node.right)) {
|
|
reportClass((node.left as Acorn.ESTree.Node));
|
|
} else if (node.type === 'Property' && isNameNode(node.key) && isClassNode(node.value)) {
|
|
reportClass((node.key as Acorn.ESTree.Node));
|
|
} else if (node.type === 'FunctionDeclaration') {
|
|
reportFunction((node.id as Acorn.ESTree.Node), node);
|
|
} else if (node.type === 'VariableDeclarator' && node.init && isFunctionNode(node.init)) {
|
|
reportFunction((node.id as Acorn.ESTree.Node), (node.init as Acorn.ESTree.Node));
|
|
} else if (node.type === 'AssignmentExpression' && isNameNode(node.left) && isFunctionNode(node.right)) {
|
|
reportFunction((node.left as Acorn.ESTree.Node), (node.right as Acorn.ESTree.Node));
|
|
} else if (
|
|
(node.type === 'MethodDefinition' || node.type === 'Property') && isNameNode(node.key) &&
|
|
isFunctionNode(node.value)) {
|
|
const namePrefix = [];
|
|
if (node.kind === 'get' || node.kind === 'set') {
|
|
namePrefix.push(node.kind);
|
|
}
|
|
if ('static' in node && node.static) {
|
|
namePrefix.push('static');
|
|
}
|
|
reportFunction((node.key as Acorn.ESTree.Node), node.value, namePrefix.join(' '));
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function reportClass(nameNode: Acorn.ESTree.Node): void {
|
|
const name = 'class ' + stringifyNameNode(nameNode);
|
|
textCursor.advance(nameNode.start);
|
|
addOutlineItem(name);
|
|
}
|
|
|
|
function reportFunction(nameNode: Acorn.ESTree.Node, functionNode: Acorn.ESTree.Node, namePrefix?: string): void {
|
|
let name = stringifyNameNode(nameNode);
|
|
const functionDeclarationNode = (functionNode as Acorn.ESTree.FunctionDeclaration);
|
|
if (functionDeclarationNode.generator) {
|
|
name = '*' + name;
|
|
}
|
|
if (namePrefix) {
|
|
name = namePrefix + ' ' + name;
|
|
}
|
|
if (functionDeclarationNode.async) {
|
|
name = 'async ' + name;
|
|
}
|
|
|
|
textCursor.advance(nameNode.start);
|
|
addOutlineItem(name, stringifyArguments((functionDeclarationNode.params as Acorn.ESTree.Node[])));
|
|
}
|
|
|
|
function isNameNode(node: Acorn.ESTree.Node): boolean {
|
|
if (!node) {
|
|
return false;
|
|
}
|
|
if (node.type === 'MemberExpression') {
|
|
return !node.computed && node.property.type === 'Identifier';
|
|
}
|
|
return node.type === 'Identifier';
|
|
}
|
|
|
|
function isFunctionNode(node: Acorn.ESTree.Node): boolean {
|
|
if (!node) {
|
|
return false;
|
|
}
|
|
return node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
|
|
}
|
|
|
|
function isClassNode(node: Acorn.ESTree.Node): boolean {
|
|
return node !== undefined && node.type === 'ClassExpression';
|
|
}
|
|
|
|
function stringifyNameNode(node: Acorn.ESTree.Node): string {
|
|
if (node.type === 'MemberExpression') {
|
|
node = (node.property as Acorn.ESTree.Node);
|
|
}
|
|
console.assert(node.type === 'Identifier', 'Cannot extract identifier from unknown type: ' + node.type);
|
|
const identifier = (node as Acorn.ESTree.Identifier);
|
|
return identifier.name;
|
|
}
|
|
|
|
function stringifyArguments(params: Acorn.ESTree.Node[]): string {
|
|
const result = [];
|
|
for (const param of params) {
|
|
if (param.type === 'Identifier') {
|
|
result.push(param.name);
|
|
} else if (param.type === 'RestElement' && param.argument.type === 'Identifier') {
|
|
result.push('...' + param.argument.name);
|
|
} else {
|
|
console.error('Error: unexpected function parameter type: ' + param.type);
|
|
}
|
|
}
|
|
return '(' + result.join(', ') + ')';
|
|
}
|
|
|
|
function addOutlineItem(title: string, subtitle?: string): void {
|
|
const line = textCursor.lineNumber();
|
|
const column = textCursor.columnNumber();
|
|
chunk.push({title, subtitle, line, column});
|
|
if (textCursor.offset() - lastReportedOffset >= chunkSize) {
|
|
chunkCallback({chunk, isLastChunk: false});
|
|
chunk = [];
|
|
lastReportedOffset = textCursor.offset();
|
|
}
|
|
}
|
|
}
|