typescript
Version:
TypeScript is a language for application scale JavaScript development
858 lines • 34.9 kB
JavaScript
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
var dispose, inner;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
dispose = value[Symbol.asyncDispose];
}
if (dispose === void 0) {
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
dispose = value[Symbol.dispose];
if (async) inner = dispose;
}
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
env.stack.push({ value: value, dispose: dispose, async: async });
}
else if (async) {
env.stack.push({ async: true });
}
return value;
};
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
return function (env) {
function fail(e) {
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
env.hasError = true;
}
var r, s = 0;
function next() {
while (r = env.stack.pop()) {
try {
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
if (r.dispose) {
var result = r.dispose.call(r.value);
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
}
else s |= 1;
}
catch (e) {
fail(e);
}
}
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
if (env.hasError) throw env.error;
}
return next();
};
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
});
import { NodeFlags } from "../../enums/nodeFlags.js";
import { SyntaxKind } from "../../enums/syntaxKind.js";
import { createSyntaxList, createToken, } from "./factory.generated.js";
import { isJSDocNodeKind, isKeywordKind, isPrivateIdentifier, isPropertyNameLiteral, isTokenKind, } from "./is.js";
import { createScanner, skipTrivia, } from "./scanner.js";
export function getTokenAtPosition(sourceFile, position) {
return getTokenAtPositionImpl(sourceFile, position, /*allowPositionInLeadingTrivia*/ true, /*includePrecedingTokenAtEndPosition*/ undefined);
}
export function getTouchingPropertyName(sourceFile, position) {
return getTokenAtPositionImpl(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, node => isPropertyNameLiteral(node) || isKeywordKind(node.kind) || isPrivateIdentifier(node));
}
export function getTouchingToken(sourceFile, position) {
return getTokenAtPositionImpl(sourceFile, position, /*allowPositionInLeadingTrivia*/ false, /*includePrecedingTokenAtEndPosition*/ undefined);
}
/**
* Finds the token that starts immediately after `previousToken` ends, searching
* within `parent`. Returns `undefined` if no such token exists.
*/
export function findNextToken(previousToken, parent, sourceFile) {
return find(parent);
function find(n) {
if (isTokenKind(n.kind) && n.pos === previousToken.end) {
// This is the token that starts at the end of previousToken – return it.
return n;
}
// Find the child node that contains `previousToken` or starts immediately after it.
let foundNode;
const visitChild = (node) => {
if (node.flags & NodeFlags.Reparsed) {
return undefined;
}
if (node.pos <= previousToken.end && node.end > previousToken.end) {
foundNode = node;
}
return undefined;
};
// Visit JSDoc children first (mirrors Go's VisitEachChildAndJSDoc).
if (n.jsDoc) {
for (const jsdoc of n.jsDoc) {
visitChild(jsdoc);
}
}
n.forEachChild(visitChild, nodes => {
if (nodes.length > 0 && foundNode === undefined) {
for (const node of nodes) {
if (node.flags & NodeFlags.Reparsed)
continue;
if (node.pos > previousToken.end)
break;
if (node.end > previousToken.end) {
foundNode = node;
break;
}
}
}
return undefined;
});
// Recurse into the found child.
if (foundNode !== undefined) {
return find(foundNode);
}
// No AST child covers the position; use the scanner to find the syntactic token.
// The scanner is initialized at `previousToken.end`, so tokenFullStart === previousToken.end.
const startPos = previousToken.end;
if (startPos >= n.pos && startPos < n.end) {
return withScanner(sourceFile, startPos, scanner => {
const token = scanner.getToken();
const tokenFullStart = scanner.getTokenFullStart();
const tokenEnd = scanner.getTokenEnd();
const flags = scanner.getTokenFlags();
return getOrCreateToken(sourceFile, token, tokenFullStart, tokenEnd, n, flags);
});
}
return undefined;
}
}
/**
* Finds the leftmost token satisfying `position < token.end`.
* If the position is in the trivia of that leftmost token, or the token is invalid,
* returns the rightmost valid token with `token.end <= position`.
* Excludes `JsxText` tokens containing only whitespace.
*/
export function findPrecedingToken(sourceFile, position) {
return findPrecedingTokenImpl(sourceFile, position, sourceFile);
}
function getTokenAtPositionImpl(sourceFile, position, allowPositionInLeadingTrivia, includePrecedingTokenAtEndPosition) {
let current = sourceFile;
let nodeAfterLeft;
const state = {
next: undefined,
prevSubtree: undefined,
left: 0,
};
const getIncludedPrecedingToken = (subtree) => {
const child = findPrecedingTokenImpl(sourceFile, position, subtree);
if (child !== undefined && child.end === position && includePrecedingTokenAtEndPosition(child)) {
return child;
}
return undefined;
};
const testNode = (node) => {
if (node.kind !== SyntaxKind.EndOfFile && node.end === position && includePrecedingTokenAtEndPosition !== undefined) {
if (state.prevSubtree !== undefined && getIncludedPrecedingToken(state.prevSubtree) !== undefined) {
return 0;
}
state.prevSubtree = node;
}
// A node "contains" the position if position < end, except nodes at the file end
// treat end as inclusive (there's nowhere else to look). This applies to the EOF
// token itself, and to JSDoc nodes reaching EOF (e.g. unterminated JSDoc comments).
if (node.end < position || node.end === position &&
node.kind !== SyntaxKind.EndOfFile &&
(!isJSDocNodeKind(node.kind) || node.end !== sourceFile.endOfFileToken.end)) {
return -1;
}
const nodePos = getPosition(node, sourceFile, allowPositionInLeadingTrivia);
if (nodePos > position) {
return 1;
}
return 0;
};
while (true) {
// Visit each child of current to find the one containing the position.
state.next = undefined;
nodeAfterLeft = undefined;
// In Strada, JSDoc nodes with a single comment represent that comment as a string
// property (not a child node), so forEachChild does not visit it. We replicate this
// by detecting single-comment NodeLists in visitList and skipping their elements in visitNode.
let skipSingleCommentChildren = false;
const visitNode = (node) => {
if (node.flags & NodeFlags.Reparsed) {
return undefined;
}
if (skipSingleCommentChildren && isJSDocCommentChildKind(node.kind)) {
return undefined;
}
if (nodeAfterLeft === undefined) {
nodeAfterLeft = node;
}
if (state.next === undefined) {
const result = testNode(node);
switch (result) {
case -1:
if (!isJSDocNodeKind(node.kind)) {
state.left = node.end;
}
nodeAfterLeft = undefined;
break;
case 0:
state.next = node;
break;
}
}
return undefined;
};
// Visit JSDoc children first, then regular children (mirrors Go's VisitEachChildAndJSDoc).
if (current.jsDoc) {
for (const jsdoc of current.jsDoc) {
visitNode(jsdoc);
}
}
current.forEachChild(visitNode, nodes => {
// Track whether this NodeList is a single-comment list that should be skipped.
// The flag affects the subsequent forEachNode(visitNode) calls for this NodeList.
skipSingleCommentChildren = isJSDocSingleCommentNodeList(nodes);
if (nodes.length === 0 || skipSingleCommentChildren) {
return undefined;
}
if (nodeAfterLeft === undefined) {
for (const node of nodes) {
if (!(node.flags & NodeFlags.Reparsed)) {
nodeAfterLeft = node;
break;
}
}
}
if (state.next === undefined) {
if (nodes.end === position && includePrecedingTokenAtEndPosition !== undefined) {
state.left = nodes.end;
nodeAfterLeft = undefined;
state.prevSubtree = nodes[nodes.length - 1];
}
else if (nodes.end <= position) {
state.left = nodes.end;
nodeAfterLeft = undefined;
}
else if (nodes.pos <= position) {
binarySearchNodeList(nodes, testNode, (node, middle, arr) => {
state.left = node.end;
nodeAfterLeft = undefined;
for (let i = middle + 1; i < arr.length; i++) {
if (!(arr[i].flags & NodeFlags.Reparsed)) {
nodeAfterLeft = arr[i];
break;
}
}
}, found => {
state.next = found;
});
}
}
return undefined;
});
// If prevSubtree was set, check if the rightmost token of prevSubtree should be returned.
if (state.prevSubtree !== undefined) {
const child = getIncludedPrecedingToken(state.prevSubtree);
if (child !== undefined) {
return child;
}
state.prevSubtree = undefined;
}
if (state.next === undefined) {
if (isTokenKind(current.kind) || shouldSkipChild(current)) {
return current;
}
// Use the scanner to find a token not stored in the AST.
const containing = current;
let end = containing.end;
const afterLeft = nodeAfterLeft;
if (afterLeft !== undefined) {
end = afterLeft.pos;
}
return withScanner(sourceFile, state.left, scanner => {
while (state.left < end) {
const token = scanner.getToken();
const tokenFullStart = scanner.getTokenFullStart();
const tokenStart = allowPositionInLeadingTrivia ? tokenFullStart : scanner.getTokenStart();
const tokenEnd = scanner.getTokenEnd();
const flags = scanner.getTokenFlags();
if (tokenEnd > end) {
break;
}
if (tokenStart <= position && position < tokenEnd) {
if (token === SyntaxKind.Identifier || !isTokenKind(token)) {
if (isJSDocNodeKind(containing.kind)) {
return containing;
}
throw new Error(`did not expect ${SyntaxKind[containing.kind]} to have ${SyntaxKind[token]} in its trivia`);
}
return getOrCreateToken(sourceFile, token, tokenFullStart, tokenEnd, containing, flags);
}
if (includePrecedingTokenAtEndPosition !== undefined && tokenEnd === position) {
const prevToken = getOrCreateToken(sourceFile, token, tokenFullStart, tokenEnd, containing, flags);
if (includePrecedingTokenAtEndPosition(prevToken)) {
return prevToken;
}
}
state.left = tokenEnd;
scanner.scan();
}
return containing;
});
}
current = state.next;
state.left = current.pos;
nodeAfterLeft = undefined;
}
}
function getPosition(node, sourceFile, allowPositionInLeadingTrivia) {
if (allowPositionInLeadingTrivia) {
return node.pos;
}
return getTokenPosOfNode(node, sourceFile, /*includeJSDoc*/ true);
}
/** @internal */
export function getTokenPosOfNode(node, sourceFile, includeJSDoc) {
// With nodes that have no width (i.e. 'Missing' nodes), we actually *don't*
// want to skip trivia because this will launch us forward to the next token.
if (nodeIsMissing(node)) {
return node.pos;
}
if (isJSDocNodeKind(node.kind) || node.kind === SyntaxKind.JsxText) {
return skipTrivia(sourceFile.text, node.pos, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
}
if (includeJSDoc && node.jsDoc && node.jsDoc.length > 0) {
return getTokenPosOfNode(node.jsDoc[0], sourceFile, /*includeJSDoc*/ false);
}
return skipTrivia(sourceFile.text, node.pos, /*stopAfterLineBreak*/ false, /*stopAtComments*/ false, /*inJSDoc*/ !!(node.flags & NodeFlags.JSDoc));
}
function nodeIsMissing(node) {
return node.pos === node.end && node.pos >= 0 && node.kind !== SyntaxKind.EndOfFile;
}
function findPrecedingTokenImpl(sourceFile, position, startNode) {
const find = (n) => {
if (isTokenKind(n.kind) && n.kind !== SyntaxKind.EndOfFile) {
return n;
}
let foundChild;
let prevChild;
// Visit JSDoc nodes first (mirrors Go's VisitEachChildAndJSDoc).
if (n.jsDoc) {
for (const jsdoc of n.jsDoc) {
if (jsdoc.flags & NodeFlags.Reparsed)
continue;
if (foundChild !== undefined)
break;
if (position < jsdoc.end && (prevChild === undefined || prevChild.end <= position)) {
foundChild = jsdoc;
}
else {
prevChild = jsdoc;
}
}
}
let skipSingleCommentChildrenImpl = false;
n.forEachChild(node => {
if (node.flags & NodeFlags.Reparsed) {
return undefined;
}
if (skipSingleCommentChildrenImpl && isJSDocCommentChildKind(node.kind)) {
return undefined;
}
if (foundChild !== undefined) {
return undefined;
}
if (position < node.end && (prevChild === undefined || prevChild.end <= position)) {
foundChild = node;
}
else {
prevChild = node;
}
return undefined;
}, nodes => {
skipSingleCommentChildrenImpl = isJSDocSingleCommentNodeList(nodes);
if (foundChild !== undefined) {
return undefined;
}
if (nodes.length > 0 && !skipSingleCommentChildrenImpl) {
const index = binarySearchForPrecedingToken(nodes, position);
if (index >= 0 && !(nodes[index].flags & NodeFlags.Reparsed)) {
foundChild = nodes[index];
}
const lookupIndex = index >= 0 ? index - 1 : nodes.length - 1;
for (let i = lookupIndex; i >= 0; i--) {
if (!(nodes[i].flags & NodeFlags.Reparsed)) {
if (prevChild === undefined) {
prevChild = nodes[i];
}
break;
}
}
}
return undefined;
});
if (foundChild !== undefined) {
const start = getTokenPosOfNode(foundChild, sourceFile, /*includeJSDoc*/ true);
if (start >= position) {
if (position >= foundChild.pos) {
// We are in the leading trivia of foundChild. Check for JSDoc nodes of n
// preceding foundChild, mirroring Go's findPrecedingToken logic.
let jsDoc;
if (n.jsDoc) {
for (let i = n.jsDoc.length - 1; i >= 0; i--) {
if (n.jsDoc[i].pos >= foundChild.pos) {
jsDoc = n.jsDoc[i];
break;
}
}
}
if (jsDoc !== undefined) {
if (position < jsDoc.end) {
return find(jsDoc);
}
return findRightmostValidToken(sourceFile, jsDoc.end, n, position);
}
return findRightmostValidToken(sourceFile, foundChild.pos, n, -1);
}
// Answer is in tokens between two visited children.
return findRightmostValidToken(sourceFile, foundChild.pos, n, position);
}
return find(foundChild);
}
if (position >= n.end) {
return findRightmostValidToken(sourceFile, n.end, n, -1);
}
return findRightmostValidToken(sourceFile, n.end, n, position);
};
return find(startNode);
}
function findRightmostValidToken(sourceFile, endPos, containingNode, position) {
if (position === -1) {
position = containingNode.end;
}
const find = (n, endPos) => {
if (isTokenKind(n.kind) && n.kind !== SyntaxKind.EndOfFile) {
return n;
}
let rightmostValidNode;
let hasChildren = false;
// Visit JSDoc nodes first (mirrors Go's VisitEachChildAndJSDoc).
if (n.jsDoc) {
hasChildren = true;
for (const jsdoc of n.jsDoc) {
if (jsdoc.flags & NodeFlags.Reparsed)
continue;
if (jsdoc.end > endPos || getTokenPosOfNode(jsdoc, sourceFile) >= position)
continue;
if (isValidPrecedingNode(jsdoc, sourceFile)) {
rightmostValidNode = jsdoc;
}
}
}
let skipSingleCommentChildren = false;
n.forEachChild(node => {
if (node.flags & NodeFlags.Reparsed) {
return undefined;
}
if (skipSingleCommentChildren && isJSDocCommentChildKind(node.kind)) {
return undefined;
}
hasChildren = true;
if (node.end > endPos || getTokenPosOfNode(node, sourceFile) >= position) {
return undefined;
}
if (isValidPrecedingNode(node, sourceFile)) {
rightmostValidNode = node;
}
return undefined;
}, nodes => {
// Skip single-comment JSDoc NodeLists (e.g. JSDocText children of a JSDoc node):
// In Go, these are stored as string properties and are never visited as children.
skipSingleCommentChildren = isJSDocSingleCommentNodeList(nodes);
if (nodes.length > 0 && !skipSingleCommentChildren) {
hasChildren = true;
for (let i = nodes.length - 1; i >= 0; i--) {
const node = nodes[i];
if (node.flags & NodeFlags.Reparsed)
continue;
if (node.end > endPos || getTokenPosOfNode(node, sourceFile) >= position)
continue;
if (isValidPrecedingNode(node, sourceFile)) {
rightmostValidNode = node;
break;
}
}
}
return undefined;
});
// Scan for syntactic tokens (e.g. `{`, `,`) between AST nodes, matching Go's
// findRightmostValidToken scanner step.
if (!shouldSkipChild(n)) {
const startPos = rightmostValidNode !== undefined ? rightmostValidNode.end : n.pos;
const targetEnd = Math.min(endPos, position);
if (startPos < targetEnd) {
const lastScannedToken = withScanner(sourceFile, startPos, scanner => {
let pos = startPos;
let lastToken;
while (pos < targetEnd) {
const tokenStart = scanner.getTokenStart();
if (tokenStart >= position)
break;
const tokenFullStart = scanner.getTokenFullStart();
const tokenEnd = scanner.getTokenEnd();
const token = scanner.getToken();
const flags = scanner.getTokenFlags();
lastToken = getOrCreateToken(sourceFile, token, tokenFullStart, tokenEnd, n, flags);
pos = tokenEnd;
scanner.scan();
}
return lastToken;
});
if (lastScannedToken !== undefined) {
return lastScannedToken;
}
}
}
if (!hasChildren) {
if (n !== containingNode) {
return n;
}
return undefined;
}
if (rightmostValidNode !== undefined) {
return find(rightmostValidNode, rightmostValidNode.end);
}
return undefined;
};
return find(containingNode, endPos);
}
function isValidPrecedingNode(node, sourceFile) {
if (node.kind === SyntaxKind.EndOfFile) {
return false;
}
const start = getTokenPosOfNode(node, sourceFile);
const width = node.end - start;
return width > 0;
}
function shouldSkipChild(node) {
return node.kind === SyntaxKind.JSDoc ||
node.kind === SyntaxKind.JSDocText ||
node.kind === SyntaxKind.JSDocTypeLiteral ||
node.kind === SyntaxKind.JSDocSignature ||
node.kind === SyntaxKind.JSDocLink ||
node.kind === SyntaxKind.JSDocLinkCode ||
node.kind === SyntaxKind.JSDocLinkPlain ||
isJSDocTag(node);
}
function isJSDocTag(node) {
return node.kind >= SyntaxKind.FirstJSDocTagNode && node.kind <= SyntaxKind.LastJSDocTagNode;
}
// In Strada, if a JSDoc node has a single comment, that comment is represented as a string
// property as a simplification, and therefore that comment is not visited by forEachChild.
// To match, we skip single-element comment NodeLists within JSDoc/JSDocTag nodes.
function isJSDocCommentChildKind(kind) {
switch (kind) {
case SyntaxKind.JSDocText:
case SyntaxKind.JSDocLink:
case SyntaxKind.JSDocLinkCode:
case SyntaxKind.JSDocLinkPlain:
return true;
default:
return false;
}
}
function isJSDocSingleCommentNodeList(nodes) {
return nodes.length === 1 && isJSDocCommentChildKind(nodes[0].kind);
}
const scannerPools = new WeakMap();
function acquireScanner(sourceFile) {
let pool = scannerPools.get(sourceFile);
if (pool === undefined) {
pool = [];
scannerPools.set(sourceFile, pool);
}
const lease = pool.pop();
if (lease === undefined) {
return createPooledScanner(sourceFile, pool);
}
// setText resets the token state (position, token, value, flags) to the start of the text.
lease.scanner.setText(sourceFile.text);
lease.scanner.setLanguageVariant(sourceFile.languageVariant);
return lease;
}
function createPooledScanner(sourceFile, pool) {
return {
scanner: createScanner(/*skipTrivia*/ true, sourceFile.languageVariant, sourceFile.text),
[Symbol.dispose]() {
// Drop per-use state so an idle scanner holds no token values or comment directives.
// The JSDoc leading-asterisk depth is never changed by this module, so it stays zero.
this.scanner.clearCommentDirectives();
this.scanner.resetTokenState(0);
pool.push(this);
},
};
}
function withScanner(sourceFile, pos, callback) {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const lease = __addDisposableResource(env_1, acquireScanner(sourceFile), false);
const { scanner } = lease;
if (pos !== undefined) {
scanner.resetTokenState(pos);
scanner.scan();
}
return callback(scanner);
}
catch (e_1) {
env_1.error = e_1;
env_1.hasError = true;
}
finally {
__disposeResources(env_1);
}
}
/**
* Returns the idle scanners pooled for `sourceFile`. Exposed for tests only.
* @internal
*/
export function getPooledScannersForTesting(sourceFile) {
return scannerPools.get(sourceFile)?.map(lease => lease.scanner) ?? emptyScannerArray;
}
const emptyScannerArray = [];
function getOrCreateToken(sourceFile, kind, pos, end, parent, _flags) {
const key = `${pos}_${end}`;
if (!sourceFile.tokenCache) {
sourceFile.tokenCache = new Map();
}
const existing = sourceFile.tokenCache.get(key);
if (existing !== undefined) {
return existing;
}
const token = createToken(kind);
token.pos = pos;
token.end = end;
token.parent = parent;
sourceFile.tokenCache.set(key, token);
return token;
}
const emptyArray = [];
function assertHasRealPosition(node) {
if (node.pos < 0 || node.end < 0) {
throw new Error("Node without a real position cannot be scanned and thus has no token nodes - use forEachChild and collect the result if that's fine");
}
}
export function getChildren(node, sourceFile = node.getSourceFile()) {
// A SyntaxList already holds its (pre-materialized) children.
if (node.kind === SyntaxKind.SyntaxList) {
return node.children;
}
if (isTokenKind(node.kind)) {
// EndOfFile may carry leading JSDoc; every other token has no children. The EndOfFile
// result must go through the cache: remote nodes rebuild .jsDoc on every access.
if (node.kind !== SyntaxKind.EndOfFile) {
return emptyArray;
}
}
else {
assertHasRealPosition(node);
}
const cache = (sourceFile.childrenCache ??= new Map());
const cached = cache.get(node);
if (cached !== undefined) {
return cached;
}
const children = node.kind === SyntaxKind.EndOfFile
? node.jsDoc ?? emptyArray
: createChildren(node, sourceFile);
cache.set(node, children);
return children;
}
function createChildren(node, sourceFile) {
const env_2 = { stack: [], error: void 0, hasError: false };
try {
const children = [];
// Inside a JSDoc comment there are no real tokens to synthesize.
if (shouldSkipChild(node)) {
node.forEachChild(child => void children.push(child));
return children;
}
// One scanner serves every run of synthetic tokens materialized for this node.
const lease = __addDisposableResource(env_2, acquireScanner(sourceFile), false);
const { scanner } = lease;
let pos = node.pos;
const processNode = (child) => {
// Reparsed subtrees (JSDoc types materialized into the AST) have positions inside
// the comment, not in this node's token range.
if (child.flags & NodeFlags.Reparsed) {
return;
}
addSyntheticNodes(children, pos, child.pos, node, sourceFile, scanner);
children.push(child);
pos = child.end;
};
const processNodes = (nodes) => {
if (nodes.length > 0 && !nodes.some(child => !(child.flags & NodeFlags.Reparsed))) {
return;
}
addSyntheticNodes(children, pos, nodes.pos, node, sourceFile, scanner);
children.push(createSyntaxListNode(nodes, node, sourceFile, scanner));
pos = nodes.end;
};
// JSDoc attached to the node is leading content, processed first.
if (node.jsDoc) {
for (const jsDoc of node.jsDoc) {
processNode(jsDoc);
}
}
pos = node.pos;
node.forEachChild(processNode, processNodes);
addSyntheticNodes(children, pos, node.end, node, sourceFile, scanner);
return children;
}
catch (e_2) {
env_2.error = e_2;
env_2.hasError = true;
}
finally {
__disposeResources(env_2);
}
}
function addSyntheticNodes(children, pos, end, parent, sourceFile, scanner) {
if (pos >= end) {
return;
}
scanner.resetTokenState(pos);
scanner.scan();
while (pos < end) {
const token = scanner.getToken();
const tokenEnd = scanner.getTokenEnd();
if (tokenEnd <= end) {
// An identifier should never appear as trivia between AST children; skip defensively.
if (token !== SyntaxKind.Identifier) {
children.push(getOrCreateToken(sourceFile, token, pos, tokenEnd, parent, scanner.getTokenFlags()));
}
}
pos = tokenEnd;
if (token === SyntaxKind.EndOfFile) {
break;
}
scanner.scan();
}
}
function createSyntaxListNode(nodes, parent, sourceFile, scanner) {
const listChildren = [];
let pos = nodes.pos;
for (const child of nodes) {
if (child.flags & NodeFlags.Reparsed) {
continue;
}
addSyntheticNodes(listChildren, pos, child.pos, parent, sourceFile, scanner);
listChildren.push(child);
pos = child.end;
}
addSyntheticNodes(listChildren, pos, nodes.end, parent, sourceFile, scanner);
const list = createSyntaxList(listChildren);
list.pos = nodes.pos;
list.end = nodes.end;
list.parent = parent;
return list;
}
export function getFirstToken(node, sourceFile = node.getSourceFile()) {
if (isTokenKind(node.kind)) {
return undefined;
}
assertHasRealPosition(node);
const children = getChildren(node, sourceFile);
const child = children.find(kid => kid.kind < SyntaxKind.FirstJSDocNode || kid.kind > SyntaxKind.LastJSDocNode);
if (child === undefined) {
return undefined;
}
return child.kind < SyntaxKind.FirstNode ? child : getFirstToken(child, sourceFile);
}
export function getLastToken(node, sourceFile = node.getSourceFile()) {
if (isTokenKind(node.kind)) {
return undefined;
}
assertHasRealPosition(node);
const children = getChildren(node, sourceFile);
const child = children.length ? children[children.length - 1] : undefined;
if (child === undefined) {
return undefined;
}
return child.kind < SyntaxKind.FirstNode ? child : getLastToken(child, sourceFile);
}
/** Binary search a node list for the node containing position. */
function binarySearchNodeList(nodes, testNode, onLeft, onMatch) {
let lo = 0;
let hi = nodes.length - 1;
while (lo <= hi) {
const mid = (lo + hi) >>> 1;
const node = nodes[mid];
if (node.flags & NodeFlags.Reparsed) {
// Skip reparsed nodes: try to find a non-reparsed node nearby
let found = false;
for (let i = mid + 1; i <= hi; i++) {
if (!(nodes[i].flags & NodeFlags.Reparsed)) {
const cmp = testNode(nodes[i]);
if (cmp < 0) {
onLeft(nodes[i], i, nodes);
lo = i + 1;
}
else if (cmp > 0) {
hi = i - 1;
}
else {
onMatch(nodes[i]);
return;
}
found = true;
break;
}
}
if (!found) {
hi = mid - 1;
}
continue;
}
const cmp = testNode(node);
if (cmp < 0) {
onLeft(node, mid, nodes);
lo = mid + 1;
}
else if (cmp > 0) {
hi = mid - 1;
}
else {
onMatch(node);
return;
}
}
}
function binarySearchForPrecedingToken(nodes, position) {
let lo = 0;
let hi = nodes.length - 1;
let result = -1;
while (lo <= hi) {
const mid = (lo + hi) >>> 1;
const node = nodes[mid];
if (node.flags & NodeFlags.Reparsed) {
lo = mid + 1;
continue;
}
if (position < node.end) {
if (mid === 0 || position >= nodes[mid - 1].end) {
result = mid;
break;
}
hi = mid - 1;
}
else {
lo = mid + 1;
}
}
return result;
}
//# sourceMappingURL=astnav.js.map