• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    ArrowFunction, AssignmentDeclarationKind, BinaryExpression, BindableElementAccessExpression,
3    BindableObjectDefinePropertyCall, BindableStaticNameExpression, BindingElement, CallExpression, CancellationToken,
4    ClassElement, ClassExpression, ClassLikeDeclaration, compareStringsCaseSensitiveUI, compareValues, concatenate,
5    ConstructorDeclaration, contains, createTextSpanFromNode, createTextSpanFromRange, Debug, Declaration,
6    DeclarationName, declarationNameToString, EntityNameExpression, EnumDeclaration, EnumMember, escapeString, ESMap,
7    ExportAssignment, Expression, factory, filterMutate, forEach, forEachChild, FunctionDeclaration, FunctionExpression,
8    FunctionLikeDeclaration, getAssignmentDeclarationKind, getBaseFileName, getElementOrPropertyAccessName,
9    getFullWidth, getNameOfDeclaration, getNameOrArgument, getNodeKind, getNodeModifiers,
10    getPropertyNameForPropertyNameNode, getSyntacticModifierFlags, getTextOfIdentifierOrLiteral, getTextOfNode,
11    hasDynamicName, hasJSDocNodes, Identifier, idText, ImportClause, InterfaceDeclaration, InternalSymbolName,
12    isAmbientModule, isArrowFunction, isBinaryExpression, isBindableStaticAccessExpression, isBindingPattern,
13    isCallExpression, isClassDeclaration, isClassLike, isDeclaration, isElementAccessExpression, isExportAssignment,
14    isExpression, isExternalModule, isFunctionDeclaration, isFunctionExpression, isIdentifier, isJSDocTypeAlias,
15    isModuleBlock, isModuleDeclaration, isObjectLiteralExpression, isParameterPropertyDeclaration, isPrivateIdentifier,
16    isPropertyAccessExpression, isPropertyAssignment, isPropertyName, isPropertyNameLiteral, isStatic,
17    isStringLiteralLike, isToken, isVariableDeclaration, lastOrUndefined, map, Map, mapDefined, ModifierFlags,
18    ModuleDeclaration, NavigationBarItem, NavigationTree, Node, NodeFlags, normalizePath, PropertyAccessExpression,
19    PropertyAssignment, PropertyDeclaration, PropertyNameLiteral, removeFileExtension, setTextRange,
20    ShorthandPropertyAssignment, SourceFile, SpreadAssignment, SyntaxKind, TextSpan, TypeElement,
21    unescapeLeadingUnderscores, VariableDeclaration,
22} from "./_namespaces/ts";
23
24/**
25 * Matches all whitespace characters in a string. Eg:
26 *
27 * "app.
28 *
29 * onactivated"
30 *
31 * matches because of the newline, whereas
32 *
33 * "app.onactivated"
34 *
35 * does not match.
36 */
37const whiteSpaceRegex = /\s+/g;
38
39/**
40 * Maximum amount of characters to return
41 * The amount was chosen arbitrarily.
42 */
43const maxLength = 150;
44
45// Keep sourceFile handy so we don't have to search for it every time we need to call `getText`.
46let curCancellationToken: CancellationToken;
47let curSourceFile: SourceFile;
48
49/**
50 * For performance, we keep navigation bar parents on a stack rather than passing them through each recursion.
51 * `parent` is the current parent and is *not* stored in parentsStack.
52 * `startNode` sets a new parent and `endNode` returns to the previous parent.
53 */
54let parentsStack: NavigationBarNode[] = [];
55let parent: NavigationBarNode;
56
57const trackedEs5ClassesStack: (ESMap<string, boolean> | undefined)[] = [];
58let trackedEs5Classes: ESMap<string, boolean> | undefined;
59
60// NavigationBarItem requires an array, but will not mutate it, so just give it this for performance.
61let emptyChildItemArray: NavigationBarItem[] = [];
62
63/**
64 * Represents a navigation bar item and its children.
65 * The returned NavigationBarItem is more complicated and doesn't include 'parent', so we use these to do work before converting.
66 */
67interface NavigationBarNode {
68    node: Node;
69    name: DeclarationName | undefined;
70    additionalNodes: Node[] | undefined;
71    parent: NavigationBarNode | undefined; // Present for all but root node
72    children: NavigationBarNode[] | undefined;
73    indent: number; // # of parents
74}
75
76/** @internal */
77export function getNavigationBarItems(sourceFile: SourceFile, cancellationToken: CancellationToken): NavigationBarItem[] {
78    curCancellationToken = cancellationToken;
79    curSourceFile = sourceFile;
80    try {
81        return map(primaryNavBarMenuItems(rootNavigationBarNode(sourceFile)), convertToPrimaryNavBarMenuItem);
82    }
83    finally {
84        reset();
85    }
86}
87
88/** @internal */
89export function getNavigationTree(sourceFile: SourceFile, cancellationToken: CancellationToken): NavigationTree {
90    curCancellationToken = cancellationToken;
91    curSourceFile = sourceFile;
92    try {
93        return convertToTree(rootNavigationBarNode(sourceFile));
94    }
95    finally {
96        reset();
97    }
98}
99
100function reset() {
101    curSourceFile = undefined!;
102    curCancellationToken = undefined!;
103    parentsStack = [];
104    parent = undefined!;
105    emptyChildItemArray = [];
106}
107
108function nodeText(node: Node): string {
109    return cleanText(node.getText(curSourceFile));
110}
111
112function navigationBarNodeKind(n: NavigationBarNode): SyntaxKind {
113    return n.node.kind;
114}
115
116function pushChild(parent: NavigationBarNode, child: NavigationBarNode): void {
117    if (parent.children) {
118        parent.children.push(child);
119    }
120    else {
121        parent.children = [child];
122    }
123}
124
125function rootNavigationBarNode(sourceFile: SourceFile): NavigationBarNode {
126    Debug.assert(!parentsStack.length);
127    const root: NavigationBarNode = { node: sourceFile, name: undefined, additionalNodes: undefined, parent: undefined, children: undefined, indent: 0 };
128    parent = root;
129    for (const statement of sourceFile.statements) {
130        addChildrenRecursively(statement);
131    }
132    endNode();
133    Debug.assert(!parent && !parentsStack.length);
134    return root;
135}
136
137function addLeafNode(node: Node, name?: DeclarationName): void {
138    pushChild(parent, emptyNavigationBarNode(node, name));
139}
140
141function emptyNavigationBarNode(node: Node, name?: DeclarationName): NavigationBarNode {
142    return {
143        node,
144        name: name || (isDeclaration(node) || isExpression(node) ? getNameOfDeclaration(node) : undefined),
145        additionalNodes: undefined,
146        parent,
147        children: undefined,
148        indent: parent.indent + 1
149    };
150}
151
152function addTrackedEs5Class(name: string) {
153    if (!trackedEs5Classes) {
154        trackedEs5Classes = new Map();
155    }
156    trackedEs5Classes.set(name, true);
157}
158function endNestedNodes(depth: number): void {
159    for (let i = 0; i < depth; i++) endNode();
160}
161function startNestedNodes(targetNode: Node, entityName: BindableStaticNameExpression) {
162    const names: PropertyNameLiteral[] = [];
163    while (!isPropertyNameLiteral(entityName)) {
164        const name = getNameOrArgument(entityName);
165        const nameText = getElementOrPropertyAccessName(entityName);
166        entityName = entityName.expression;
167        if (nameText === "prototype" || isPrivateIdentifier(name)) continue;
168        names.push(name);
169    }
170    names.push(entityName);
171    for (let i = names.length - 1; i > 0; i--) {
172        const name = names[i];
173        startNode(targetNode, name);
174    }
175    return [names.length - 1, names[0]] as const;
176}
177
178/**
179 * Add a new level of NavigationBarNodes.
180 * This pushes to the stack, so you must call `endNode` when you are done adding to this node.
181 */
182function startNode(node: Node, name?: DeclarationName): void {
183    const navNode: NavigationBarNode = emptyNavigationBarNode(node, name);
184    pushChild(parent, navNode);
185
186    // Save the old parent
187    parentsStack.push(parent);
188    trackedEs5ClassesStack.push(trackedEs5Classes);
189    trackedEs5Classes = undefined;
190    parent = navNode;
191}
192
193/** Call after calling `startNode` and adding children to it. */
194function endNode(): void {
195    if (parent.children) {
196        mergeChildren(parent.children, parent);
197        sortChildren(parent.children);
198    }
199    parent = parentsStack.pop()!;
200    trackedEs5Classes = trackedEs5ClassesStack.pop();
201}
202
203function addNodeWithRecursiveChild(node: Node, child: Node | undefined, name?: DeclarationName): void {
204    startNode(node, name);
205    addChildrenRecursively(child);
206    endNode();
207}
208
209function addNodeWithRecursiveInitializer(node: VariableDeclaration | PropertyAssignment | BindingElement | PropertyDeclaration): void {
210    if (node.initializer && isFunctionOrClassExpression(node.initializer)) {
211        startNode(node);
212        forEachChild(node.initializer, addChildrenRecursively);
213        endNode();
214    }
215    else {
216        addNodeWithRecursiveChild(node, node.initializer);
217    }
218}
219
220/**
221 * Historically, we've elided dynamic names from the nav tree (including late bound names),
222 * but included certain "well known" symbol names. While we no longer distinguish those well-known
223 * symbols from other unique symbols, we do the below to retain those members in the nav tree.
224 */
225function hasNavigationBarName(node: Declaration) {
226    return !hasDynamicName(node) ||
227        (
228            node.kind !== SyntaxKind.BinaryExpression &&
229            isPropertyAccessExpression(node.name.expression) &&
230            isIdentifier(node.name.expression.expression) &&
231            idText(node.name.expression.expression) === "Symbol"
232        );
233}
234
235/** Look for navigation bar items in node's subtree, adding them to the current `parent`. */
236function addChildrenRecursively(node: Node | undefined): void {
237    curCancellationToken.throwIfCancellationRequested();
238
239    if (!node || isToken(node)) {
240        return;
241    }
242
243    switch (node.kind) {
244        case SyntaxKind.Constructor:
245            // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it.
246            const ctr = node as ConstructorDeclaration;
247            addNodeWithRecursiveChild(ctr, ctr.body);
248
249            // Parameter properties are children of the class, not the constructor.
250            for (const param of ctr.parameters) {
251                if (isParameterPropertyDeclaration(param, ctr)) {
252                    addLeafNode(param);
253                }
254            }
255            break;
256
257        case SyntaxKind.MethodDeclaration:
258        case SyntaxKind.GetAccessor:
259        case SyntaxKind.SetAccessor:
260        case SyntaxKind.MethodSignature:
261            if (hasNavigationBarName(node as ClassElement | TypeElement)) {
262                addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body);
263            }
264            break;
265
266        case SyntaxKind.PropertyDeclaration:
267            if (hasNavigationBarName(node as ClassElement)) {
268                addNodeWithRecursiveInitializer(node as PropertyDeclaration);
269            }
270            break;
271        case SyntaxKind.PropertySignature:
272            if (hasNavigationBarName(node as TypeElement)) {
273                addLeafNode(node);
274            }
275            break;
276
277        case SyntaxKind.ImportClause:
278            const importClause = node as ImportClause;
279            // Handle default import case e.g.:
280            //    import d from "mod";
281            if (importClause.name) {
282                addLeafNode(importClause.name);
283            }
284
285            // Handle named bindings in imports e.g.:
286            //    import * as NS from "mod";
287            //    import {a, b as B} from "mod";
288            const { namedBindings } = importClause;
289            if (namedBindings) {
290                if (namedBindings.kind === SyntaxKind.NamespaceImport) {
291                    addLeafNode(namedBindings);
292                }
293                else {
294                    for (const element of namedBindings.elements) {
295                        addLeafNode(element);
296                    }
297                }
298            }
299            break;
300
301        case SyntaxKind.ShorthandPropertyAssignment:
302            addNodeWithRecursiveChild(node, (node as ShorthandPropertyAssignment).name);
303            break;
304        case SyntaxKind.SpreadAssignment:
305            const { expression } = node as SpreadAssignment;
306            // Use the expression as the name of the SpreadAssignment, otherwise show as <unknown>.
307            isIdentifier(expression) ? addLeafNode(node, expression) : addLeafNode(node);
308            break;
309        case SyntaxKind.BindingElement:
310        case SyntaxKind.PropertyAssignment:
311        case SyntaxKind.VariableDeclaration: {
312            const child = node as VariableDeclaration | PropertyAssignment | BindingElement;
313            if (isBindingPattern(child.name)) {
314                addChildrenRecursively(child.name);
315            }
316            else {
317                addNodeWithRecursiveInitializer(child);
318            }
319            break;
320        }
321        case SyntaxKind.FunctionDeclaration:
322            const nameNode = (node as FunctionLikeDeclaration).name;
323            // If we see a function declaration track as a possible ES5 class
324            if (nameNode && isIdentifier(nameNode)) {
325                addTrackedEs5Class(nameNode.text);
326            }
327            addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body);
328            break;
329        case SyntaxKind.ArrowFunction:
330        case SyntaxKind.FunctionExpression:
331            addNodeWithRecursiveChild(node, (node as FunctionLikeDeclaration).body);
332            break;
333
334        case SyntaxKind.EnumDeclaration:
335            startNode(node);
336            for (const member of (node as EnumDeclaration).members) {
337                if (!isComputedProperty(member)) {
338                    addLeafNode(member);
339                }
340            }
341            endNode();
342            break;
343
344        case SyntaxKind.ClassDeclaration:
345        case SyntaxKind.ClassExpression:
346        case SyntaxKind.StructDeclaration:
347        case SyntaxKind.InterfaceDeclaration:
348            startNode(node);
349            for (const member of (node as InterfaceDeclaration).members) {
350                addChildrenRecursively(member);
351            }
352            endNode();
353            break;
354
355        case SyntaxKind.ModuleDeclaration:
356            addNodeWithRecursiveChild(node, getInteriorModule(node as ModuleDeclaration).body);
357            break;
358
359        case SyntaxKind.ExportAssignment: {
360            const expression = (node as ExportAssignment).expression;
361            const child = isObjectLiteralExpression(expression) || isCallExpression(expression) ? expression :
362                isArrowFunction(expression) || isFunctionExpression(expression) ? expression.body : undefined;
363            if (child) {
364                startNode(node);
365                addChildrenRecursively(child);
366                endNode();
367            }
368            else {
369                addLeafNode(node);
370            }
371            break;
372        }
373        case SyntaxKind.ExportSpecifier:
374        case SyntaxKind.ImportEqualsDeclaration:
375        case SyntaxKind.IndexSignature:
376        case SyntaxKind.CallSignature:
377        case SyntaxKind.ConstructSignature:
378        case SyntaxKind.TypeAliasDeclaration:
379            addLeafNode(node);
380            break;
381
382        case SyntaxKind.CallExpression:
383        case SyntaxKind.BinaryExpression: {
384            const special = getAssignmentDeclarationKind(node as BinaryExpression);
385            switch (special) {
386                case AssignmentDeclarationKind.ExportsProperty:
387                case AssignmentDeclarationKind.ModuleExports:
388                    addNodeWithRecursiveChild(node, (node as BinaryExpression).right);
389                    return;
390                case AssignmentDeclarationKind.Prototype:
391                case AssignmentDeclarationKind.PrototypeProperty: {
392                    const binaryExpression = (node as BinaryExpression);
393                    const assignmentTarget = binaryExpression.left as PropertyAccessExpression;
394
395                    const prototypeAccess = special === AssignmentDeclarationKind.PrototypeProperty ?
396                        assignmentTarget.expression as PropertyAccessExpression :
397                        assignmentTarget;
398
399                    let depth = 0;
400                    let className: PropertyNameLiteral;
401                    // If we see a prototype assignment, start tracking the target as a class
402                    // This is only done for simple classes not nested assignments.
403                    if (isIdentifier(prototypeAccess.expression)) {
404                        addTrackedEs5Class(prototypeAccess.expression.text);
405                        className = prototypeAccess.expression;
406                    }
407                    else {
408                        [depth, className] = startNestedNodes(binaryExpression, prototypeAccess.expression as EntityNameExpression);
409                    }
410                    if (special === AssignmentDeclarationKind.Prototype) {
411                        if (isObjectLiteralExpression(binaryExpression.right)) {
412                            if (binaryExpression.right.properties.length > 0) {
413                                startNode(binaryExpression, className);
414                                    forEachChild(binaryExpression.right, addChildrenRecursively);
415                                endNode();
416                            }
417                        }
418                    }
419                    else if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) {
420                        addNodeWithRecursiveChild(node,
421                            binaryExpression.right,
422                            className);
423                    }
424                    else {
425                        startNode(binaryExpression, className);
426                            addNodeWithRecursiveChild(node, binaryExpression.right, assignmentTarget.name);
427                        endNode();
428                    }
429                    endNestedNodes(depth);
430                    return;
431                }
432                case AssignmentDeclarationKind.ObjectDefinePropertyValue:
433                case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: {
434                    const defineCall = node as BindableObjectDefinePropertyCall;
435                    const className = special === AssignmentDeclarationKind.ObjectDefinePropertyValue ?
436                        defineCall.arguments[0] :
437                        (defineCall.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression;
438
439                    const memberName = defineCall.arguments[1];
440                    const [depth, classNameIdentifier] = startNestedNodes(node, className);
441                        startNode(node, classNameIdentifier);
442                            startNode(node, setTextRange(factory.createIdentifier(memberName.text), memberName));
443                                addChildrenRecursively((node as CallExpression).arguments[2]);
444                            endNode();
445                        endNode();
446                    endNestedNodes(depth);
447                    return;
448                }
449                case AssignmentDeclarationKind.Property: {
450                    const binaryExpression = (node as BinaryExpression);
451                    const assignmentTarget = binaryExpression.left as PropertyAccessExpression | BindableElementAccessExpression;
452                    const targetFunction = assignmentTarget.expression;
453                    if (isIdentifier(targetFunction) && getElementOrPropertyAccessName(assignmentTarget) !== "prototype" &&
454                        trackedEs5Classes && trackedEs5Classes.has(targetFunction.text)) {
455                        if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) {
456                            addNodeWithRecursiveChild(node, binaryExpression.right, targetFunction);
457                        }
458                        else if (isBindableStaticAccessExpression(assignmentTarget)) {
459                            startNode(binaryExpression, targetFunction);
460                                addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, getNameOrArgument(assignmentTarget));
461                            endNode();
462                        }
463                        return;
464                    }
465                    break;
466                }
467                case AssignmentDeclarationKind.ThisProperty:
468                case AssignmentDeclarationKind.None:
469                case AssignmentDeclarationKind.ObjectDefinePropertyExports:
470                    break;
471                default:
472                    Debug.assertNever(special);
473            }
474        }
475        // falls through
476
477        default:
478            if (hasJSDocNodes(node)) {
479                forEach(node.jsDoc, jsDoc => {
480                    forEach(jsDoc.tags, tag => {
481                        if (isJSDocTypeAlias(tag)) {
482                            addLeafNode(tag);
483                        }
484                    });
485                });
486            }
487
488            forEachChild(node, addChildrenRecursively);
489    }
490}
491
492/** Merge declarations of the same kind. */
493function mergeChildren(children: NavigationBarNode[], node: NavigationBarNode): void {
494    const nameToItems = new Map<string, NavigationBarNode | NavigationBarNode[]>();
495    filterMutate(children, (child, index) => {
496        const declName = child.name || getNameOfDeclaration(child.node as Declaration);
497        const name = declName && nodeText(declName);
498        if (!name) {
499            // Anonymous items are never merged.
500            return true;
501        }
502
503        const itemsWithSameName = nameToItems.get(name);
504        if (!itemsWithSameName) {
505            nameToItems.set(name, child);
506            return true;
507        }
508
509        if (itemsWithSameName instanceof Array) {
510            for (const itemWithSameName of itemsWithSameName) {
511                if (tryMerge(itemWithSameName, child, index, node)) {
512                    return false;
513                }
514            }
515            itemsWithSameName.push(child);
516            return true;
517        }
518        else {
519            const itemWithSameName = itemsWithSameName;
520            if (tryMerge(itemWithSameName, child, index, node)) {
521                return false;
522            }
523            nameToItems.set(name, [itemWithSameName, child]);
524            return true;
525        }
526    });
527}
528const isEs5ClassMember: Record<AssignmentDeclarationKind, boolean> = {
529    [AssignmentDeclarationKind.Property]: true,
530    [AssignmentDeclarationKind.PrototypeProperty]: true,
531    [AssignmentDeclarationKind.ObjectDefinePropertyValue]: true,
532    [AssignmentDeclarationKind.ObjectDefinePrototypeProperty]: true,
533    [AssignmentDeclarationKind.None]: false,
534    [AssignmentDeclarationKind.ExportsProperty]: false,
535    [AssignmentDeclarationKind.ModuleExports]: false,
536    [AssignmentDeclarationKind.ObjectDefinePropertyExports]: false,
537    [AssignmentDeclarationKind.Prototype]: true,
538    [AssignmentDeclarationKind.ThisProperty]: false,
539};
540function tryMergeEs5Class(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean | undefined {
541    function isPossibleConstructor(node: Node) {
542        return isFunctionExpression(node) || isFunctionDeclaration(node) || isVariableDeclaration(node);
543    }
544    const bAssignmentDeclarationKind = isBinaryExpression(b.node) || isCallExpression(b.node) ?
545        getAssignmentDeclarationKind(b.node) :
546        AssignmentDeclarationKind.None;
547
548    const aAssignmentDeclarationKind = isBinaryExpression(a.node) || isCallExpression(a.node) ?
549        getAssignmentDeclarationKind(a.node) :
550        AssignmentDeclarationKind.None;
551
552    // We treat this as an es5 class and merge the nodes in in one of several cases
553    if ((isEs5ClassMember[bAssignmentDeclarationKind] && isEs5ClassMember[aAssignmentDeclarationKind]) // merge two class elements
554        || (isPossibleConstructor(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // ctor function & member
555        || (isPossibleConstructor(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & ctor function
556        || (isClassDeclaration(a.node) && isSynthesized(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // class (generated) & member
557        || (isClassDeclaration(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & class (generated)
558        || (isClassDeclaration(a.node) && isSynthesized(a.node) && isPossibleConstructor(b.node)) // class (generated) & ctor
559        || (isClassDeclaration(b.node) && isPossibleConstructor(a.node) && isSynthesized(a.node)) // ctor & class (generated)
560        ) {
561
562        let lastANode = a.additionalNodes && lastOrUndefined(a.additionalNodes) || a.node;
563
564        if ((!isClassDeclaration(a.node) && !isClassDeclaration(b.node)) // If neither outline node is a class
565            || isPossibleConstructor(a.node) || isPossibleConstructor(b.node) // If either function is a constructor function
566            ) {
567            const ctorFunction = isPossibleConstructor(a.node) ? a.node :
568                isPossibleConstructor(b.node) ? b.node :
569                undefined;
570
571            if (ctorFunction !== undefined) {
572                const ctorNode = setTextRange(
573                    factory.createConstructorDeclaration(/* modifiers */ undefined, [], /* body */ undefined),
574                    ctorFunction);
575                const ctor = emptyNavigationBarNode(ctorNode);
576                ctor.indent = a.indent + 1;
577                ctor.children = a.node === ctorFunction ? a.children : b.children;
578                a.children = a.node === ctorFunction ? concatenate([ctor], b.children || [b]) : concatenate(a.children || [{ ...a }], [ctor]);
579            }
580            else {
581                if (a.children || b.children) {
582                    a.children = concatenate(a.children || [{ ...a }], b.children || [b]);
583                    if (a.children) {
584                        mergeChildren(a.children, a);
585                        sortChildren(a.children);
586                    }
587                }
588            }
589
590            lastANode = a.node = setTextRange(factory.createClassDeclaration(
591                /* modifiers */ undefined,
592                a.name as Identifier || factory.createIdentifier("__class__"),
593                /* typeParameters */ undefined,
594                /* heritageClauses */ undefined,
595                []
596            ), a.node);
597        }
598        else {
599            a.children = concatenate(a.children, b.children);
600            if (a.children) {
601                mergeChildren(a.children, a);
602            }
603        }
604
605        const bNode = b.node;
606        // We merge if the outline node previous to b (bIndex - 1) is already part of the current class
607        // We do this so that statements between class members that do not generate outline nodes do not split up the class outline:
608        // Ex This should produce one outline node C:
609        //    function C() {}; a = 1; C.prototype.m = function () {}
610        // Ex This will produce 3 outline nodes: C, a, C
611        //    function C() {}; let a = 1; C.prototype.m = function () {}
612        if (parent.children![bIndex - 1].node.end === lastANode.end) {
613            setTextRange(lastANode, { pos: lastANode.pos, end: bNode.end });
614        }
615        else {
616            if (!a.additionalNodes) a.additionalNodes = [];
617            a.additionalNodes.push(setTextRange(factory.createClassDeclaration(
618                /* modifiers */ undefined,
619                a.name as Identifier || factory.createIdentifier("__class__"),
620                /* typeParameters */ undefined,
621                /* heritageClauses */ undefined,
622                []
623            ), b.node));
624        }
625        return true;
626    }
627    return bAssignmentDeclarationKind === AssignmentDeclarationKind.None ? false : true;
628}
629
630function tryMerge(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean {
631    // const v = false as boolean;
632    if (tryMergeEs5Class(a, b, bIndex, parent)) {
633        return true;
634    }
635    if (shouldReallyMerge(a.node, b.node, parent)) {
636        merge(a, b);
637        return true;
638    }
639    return false;
640}
641
642/** a and b have the same name, but they may not be mergeable. */
643function shouldReallyMerge(a: Node, b: Node, parent: NavigationBarNode): boolean {
644    if (a.kind !== b.kind || a.parent !== b.parent && !(isOwnChild(a, parent) && isOwnChild(b, parent))) {
645        return false;
646    }
647    switch (a.kind) {
648        case SyntaxKind.PropertyDeclaration:
649        case SyntaxKind.MethodDeclaration:
650        case SyntaxKind.GetAccessor:
651        case SyntaxKind.SetAccessor:
652            return isStatic(a) === isStatic(b);
653        case SyntaxKind.ModuleDeclaration:
654            return areSameModule(a as ModuleDeclaration, b as ModuleDeclaration)
655                && getFullyQualifiedModuleName(a as ModuleDeclaration) === getFullyQualifiedModuleName(b as ModuleDeclaration);
656        default:
657            return true;
658    }
659}
660
661function isSynthesized(node: Node) {
662    return !!(node.flags & NodeFlags.Synthesized);
663}
664
665// We want to merge own children like `I` in in `module A { interface I {} } module A { interface I {} }`
666// We don't want to merge unrelated children like `m` in `const o = { a: { m() {} }, b: { m() {} } };`
667function isOwnChild(n: Node, parent: NavigationBarNode): boolean {
668    const par = isModuleBlock(n.parent) ? n.parent.parent : n.parent;
669    return par === parent.node || contains(parent.additionalNodes, par);
670}
671
672// We use 1 NavNode to represent 'A.B.C', but there are multiple source nodes.
673// Only merge module nodes that have the same chain. Don't merge 'A.B.C' with 'A'!
674function areSameModule(a: ModuleDeclaration, b: ModuleDeclaration): boolean {
675    if (!a.body || !b.body) {
676        return a.body === b.body;
677    }
678    return a.body.kind === b.body.kind && (a.body.kind !== SyntaxKind.ModuleDeclaration || areSameModule(a.body as ModuleDeclaration, b.body as ModuleDeclaration));
679}
680
681/** Merge source into target. Source should be thrown away after this is called. */
682function merge(target: NavigationBarNode, source: NavigationBarNode): void {
683    target.additionalNodes = target.additionalNodes || [];
684    target.additionalNodes.push(source.node);
685    if (source.additionalNodes) {
686        target.additionalNodes.push(...source.additionalNodes);
687    }
688
689    target.children = concatenate(target.children, source.children);
690    if (target.children) {
691        mergeChildren(target.children, target);
692        sortChildren(target.children);
693    }
694}
695
696/** Recursively ensure that each NavNode's children are in sorted order. */
697function sortChildren(children: NavigationBarNode[]): void {
698    children.sort(compareChildren);
699}
700
701function compareChildren(child1: NavigationBarNode, child2: NavigationBarNode) {
702    return compareStringsCaseSensitiveUI(tryGetName(child1.node)!, tryGetName(child2.node)!) // TODO: GH#18217
703        || compareValues(navigationBarNodeKind(child1), navigationBarNodeKind(child2));
704}
705
706/**
707 * This differs from getItemName because this is just used for sorting.
708 * We only sort nodes by name that have a more-or-less "direct" name, as opposed to `new()` and the like.
709 * So `new()` can still come before an `aardvark` method.
710 */
711function tryGetName(node: Node): string | undefined {
712    if (node.kind === SyntaxKind.ModuleDeclaration) {
713        return getModuleName(node as ModuleDeclaration);
714    }
715
716    const declName = getNameOfDeclaration(node as Declaration);
717    if (declName && isPropertyName(declName)) {
718        const propertyName = getPropertyNameForPropertyNameNode(declName);
719        return propertyName && unescapeLeadingUnderscores(propertyName);
720    }
721    switch (node.kind) {
722        case SyntaxKind.FunctionExpression:
723        case SyntaxKind.ArrowFunction:
724        case SyntaxKind.ClassExpression:
725            return getFunctionOrClassName(node as FunctionExpression | ArrowFunction | ClassExpression);
726        default:
727            return undefined;
728    }
729}
730
731function getItemName(node: Node, name: Node | undefined): string {
732    if (node.kind === SyntaxKind.ModuleDeclaration) {
733        return cleanText(getModuleName(node as ModuleDeclaration));
734    }
735
736    if (name) {
737        const text = isIdentifier(name) ? name.text
738            : isElementAccessExpression(name) ? `[${nodeText(name.argumentExpression)}]`
739            : nodeText(name);
740        if (text.length > 0) {
741            return cleanText(text);
742        }
743    }
744
745    switch (node.kind) {
746        case SyntaxKind.SourceFile:
747            const sourceFile = node as SourceFile;
748            return isExternalModule(sourceFile)
749                ? `"${escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName))))}"`
750                : "<global>";
751        case SyntaxKind.ExportAssignment:
752            return isExportAssignment(node) && node.isExportEquals ? InternalSymbolName.ExportEquals : InternalSymbolName.Default;
753
754        case SyntaxKind.ArrowFunction:
755        case SyntaxKind.FunctionDeclaration:
756        case SyntaxKind.FunctionExpression:
757        case SyntaxKind.ClassDeclaration:
758        case SyntaxKind.ClassExpression:
759        case SyntaxKind.StructDeclaration:
760            if (getSyntacticModifierFlags(node) & ModifierFlags.Default) {
761                return "default";
762            }
763            // We may get a string with newlines or other whitespace in the case of an object dereference
764            // (eg: "app\n.onactivated"), so we should remove the whitespace for readability in the
765            // navigation bar.
766            return getFunctionOrClassName(node as ArrowFunction | FunctionExpression | ClassExpression);
767        case SyntaxKind.Constructor:
768            return "constructor";
769        case SyntaxKind.ConstructSignature:
770            return "new()";
771        case SyntaxKind.CallSignature:
772            return "()";
773        case SyntaxKind.IndexSignature:
774            return "[]";
775        default:
776            return "<unknown>";
777    }
778}
779
780/** Flattens the NavNode tree to a list of items to appear in the primary navbar menu. */
781function primaryNavBarMenuItems(root: NavigationBarNode): NavigationBarNode[] {
782    // The primary (middle) navbar menu displays the general code navigation hierarchy, similar to the navtree.
783    // The secondary (right) navbar menu displays the child items of whichever primary item is selected.
784    // Some less interesting items without their own child navigation items (e.g. a local variable declaration) only show up in the secondary menu.
785    const primaryNavBarMenuItems: NavigationBarNode[] = [];
786    function recur(item: NavigationBarNode) {
787        if (shouldAppearInPrimaryNavBarMenu(item)) {
788            primaryNavBarMenuItems.push(item);
789            if (item.children) {
790                for (const child of item.children) {
791                    recur(child);
792                }
793            }
794        }
795    }
796    recur(root);
797    return primaryNavBarMenuItems;
798
799    /** Determines if a node should appear in the primary navbar menu. */
800    function shouldAppearInPrimaryNavBarMenu(item: NavigationBarNode): boolean {
801        // Items with children should always appear in the primary navbar menu.
802        if (item.children) {
803            return true;
804        }
805
806        // Some nodes are otherwise important enough to always include in the primary navigation menu.
807        switch (navigationBarNodeKind(item)) {
808            case SyntaxKind.ClassDeclaration:
809            case SyntaxKind.ClassExpression:
810            case SyntaxKind.StructDeclaration:
811            case SyntaxKind.EnumDeclaration:
812            case SyntaxKind.InterfaceDeclaration:
813            case SyntaxKind.ModuleDeclaration:
814            case SyntaxKind.SourceFile:
815            case SyntaxKind.TypeAliasDeclaration:
816            case SyntaxKind.JSDocTypedefTag:
817            case SyntaxKind.JSDocCallbackTag:
818                return true;
819
820            case SyntaxKind.ArrowFunction:
821            case SyntaxKind.FunctionDeclaration:
822            case SyntaxKind.FunctionExpression:
823                return isTopLevelFunctionDeclaration(item);
824
825            default:
826                return false;
827        }
828        function isTopLevelFunctionDeclaration(item: NavigationBarNode): boolean {
829            if (!(item.node as FunctionDeclaration).body) {
830                return false;
831            }
832
833            switch (navigationBarNodeKind(item.parent!)) {
834                case SyntaxKind.ModuleBlock:
835                case SyntaxKind.SourceFile:
836                case SyntaxKind.MethodDeclaration:
837                case SyntaxKind.Constructor:
838                    return true;
839                default:
840                    return false;
841            }
842        }
843    }
844}
845
846function convertToTree(n: NavigationBarNode): NavigationTree {
847    return {
848        text: getItemName(n.node, n.name),
849        kind: getNodeKind(n.node),
850        kindModifiers: getModifiers(n.node),
851        spans: getSpans(n),
852        nameSpan: n.name && getNodeSpan(n.name),
853        childItems: map(n.children, convertToTree)
854    };
855}
856
857function convertToPrimaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem {
858    return {
859        text: getItemName(n.node, n.name),
860        kind: getNodeKind(n.node),
861        kindModifiers: getModifiers(n.node),
862        spans: getSpans(n),
863        childItems: map(n.children, convertToSecondaryNavBarMenuItem) || emptyChildItemArray,
864        indent: n.indent,
865        bolded: false,
866        grayed: false
867    };
868
869    function convertToSecondaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem {
870        return {
871            text: getItemName(n.node, n.name),
872            kind: getNodeKind(n.node),
873            kindModifiers: getNodeModifiers(n.node),
874            spans: getSpans(n),
875            childItems: emptyChildItemArray,
876            indent: 0,
877            bolded: false,
878            grayed: false
879        };
880    }
881}
882
883function getSpans(n: NavigationBarNode): TextSpan[] {
884    const spans = [getNodeSpan(n.node)];
885    if (n.additionalNodes) {
886        for (const node of n.additionalNodes) {
887            spans.push(getNodeSpan(node));
888        }
889    }
890    return spans;
891}
892
893function getModuleName(moduleDeclaration: ModuleDeclaration): string {
894    // We want to maintain quotation marks.
895    if (isAmbientModule(moduleDeclaration)) {
896        return getTextOfNode(moduleDeclaration.name);
897    }
898
899    return getFullyQualifiedModuleName(moduleDeclaration);
900}
901
902function getFullyQualifiedModuleName(moduleDeclaration: ModuleDeclaration): string {
903    // Otherwise, we need to aggregate each identifier to build up the qualified name.
904    const result = [getTextOfIdentifierOrLiteral(moduleDeclaration.name)];
905    while (moduleDeclaration.body && moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) {
906        moduleDeclaration = moduleDeclaration.body;
907        result.push(getTextOfIdentifierOrLiteral(moduleDeclaration.name));
908    }
909    return result.join(".");
910}
911
912/**
913 * For 'module A.B.C', we want to get the node for 'C'.
914 * We store 'A' as associated with a NavNode, and use getModuleName to traverse down again.
915 */
916function getInteriorModule(decl: ModuleDeclaration): ModuleDeclaration {
917    return decl.body && isModuleDeclaration(decl.body) ? getInteriorModule(decl.body) : decl;
918}
919
920function isComputedProperty(member: EnumMember): boolean {
921    return !member.name || member.name.kind === SyntaxKind.ComputedPropertyName;
922}
923
924function getNodeSpan(node: Node): TextSpan {
925    return node.kind === SyntaxKind.SourceFile ? createTextSpanFromRange(node) : createTextSpanFromNode(node, curSourceFile);
926}
927
928function getModifiers(node: Node): string {
929    if (node.parent && node.parent.kind === SyntaxKind.VariableDeclaration) {
930        node = node.parent;
931    }
932    return getNodeModifiers(node);
933}
934
935function getFunctionOrClassName(node: FunctionExpression | FunctionDeclaration | ArrowFunction | ClassLikeDeclaration): string {
936    const { parent } = node;
937    if (node.name && getFullWidth(node.name) > 0) {
938        return cleanText(declarationNameToString(node.name));
939    }
940    // See if it is a var initializer. If so, use the var name.
941    else if (isVariableDeclaration(parent)) {
942        return cleanText(declarationNameToString(parent.name));
943    }
944    // See if it is of the form "<expr> = function() {...}". If so, use the text from the left-hand side.
945    else if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken) {
946        return nodeText(parent.left).replace(whiteSpaceRegex, "");
947    }
948    // See if it is a property assignment, and if so use the property name
949    else if (isPropertyAssignment(parent)) {
950        return nodeText(parent.name);
951    }
952    // Default exports are named "default"
953    else if (getSyntacticModifierFlags(node) & ModifierFlags.Default) {
954        return "default";
955    }
956    else if (isClassLike(node)) {
957        return "<class>";
958    }
959    else if (isCallExpression(parent)) {
960        let name = getCalledExpressionName(parent.expression);
961        if (name !== undefined) {
962            name = cleanText(name);
963
964            if (name.length > maxLength) {
965                return `${name} callback`;
966            }
967
968            const args = cleanText(mapDefined(parent.arguments, a => isStringLiteralLike(a) ? a.getText(curSourceFile) : undefined).join(", "));
969            return `${name}(${args}) callback`;
970        }
971    }
972    return "<function>";
973}
974
975// See also 'tryGetPropertyAccessOrIdentifierToString'
976function getCalledExpressionName(expr: Expression): string | undefined {
977    if (isIdentifier(expr)) {
978        return expr.text;
979    }
980    else if (isPropertyAccessExpression(expr)) {
981        const left = getCalledExpressionName(expr.expression);
982        const right = expr.name.text;
983        return left === undefined ? right : `${left}.${right}`;
984    }
985    else {
986        return undefined;
987    }
988}
989
990function isFunctionOrClassExpression(node: Node): node is ArrowFunction | FunctionExpression | ClassExpression {
991    switch (node.kind) {
992        case SyntaxKind.ArrowFunction:
993        case SyntaxKind.FunctionExpression:
994        case SyntaxKind.ClassExpression:
995            return true;
996        default:
997            return false;
998    }
999}
1000
1001function cleanText(text: string): string {
1002    // Truncate to maximum amount of characters as we don't want to do a big replace operation.
1003    text = text.length > maxLength ? text.substring(0, maxLength) + "..." : text;
1004
1005    // Replaces ECMAScript line terminators and removes the trailing `\` from each line:
1006    // \n - Line Feed
1007    // \r - Carriage Return
1008    // \u2028 - Line separator
1009    // \u2029 - Paragraph separator
1010    return text.replace(/\\?(\r?\n|\r|\u2028|\u2029)/g, "");
1011}
1012