UNPKG

typescript

Version:

TypeScript is a language for application scale JavaScript development

858 lines • 34.9 kB
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