Skip to content

Commit 86cce79

Browse files
author
Andy Hanson
committed
Fix sort order for toplevel declarations in nested expressions
1 parent d80677b commit 86cce79

4 files changed

Lines changed: 84 additions & 45 deletions

File tree

src/harness/fourslash.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,17 @@ namespace FourSlash {
19891989
return result;
19901990
}
19911991

1992+
public verifyNavigationBarIndex(name: string, index: number) {
1993+
const items = this.languageService.getNavigationBarItems(this.activeFile.fileName);
1994+
if (!items[index]) {
1995+
this.raiseError(`verifyNavigationBarIndex failed - No item at index ${index}`);
1996+
}
1997+
const actual = items[index].text;
1998+
if (actual !== name) {
1999+
this.raiseError(`verifyNavigationBarIndex failed - Item at index ${index} is named ${actual} instead of ${name}.`);
2000+
}
2001+
}
2002+
19922003
public verifyNavigationBarContains(name: string, kind: string) {
19932004
const items = this.languageService.getNavigationBarItems(this.activeFile.fileName);
19942005

@@ -3047,6 +3058,10 @@ namespace FourSlashInterface {
30473058
this.state.verifyNavigationBarCount(count);
30483059
}
30493060

3061+
public navigationBarIndex(name: string, index: number) {
3062+
this.state.verifyNavigationBarIndex(name, index);
3063+
}
3064+
30503065
// TODO: figure out what to do with the unused arguments.
30513066
public navigationBarContains(
30523067
name: string,

src/services/navigationBar.ts

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,13 @@ namespace ts.NavigationBar {
138138
}
139139

140140
function sortNodes(nodes: Node[]): Node[] {
141-
return nodes.slice(0).sort((n1: Declaration, n2: Declaration) => {
141+
const sortedCopy = nodes.slice(0);
142+
doSortNodes(sortedCopy);
143+
return sortedCopy;
144+
}
145+
146+
function doSortNodes(nodes: Node[]): void {
147+
nodes.sort((n1: Declaration, n2: Declaration) => {
142148
if (n1.name && n2.name) {
143149
return getPropertyNameForPropertyNameNode(n1.name).localeCompare(getPropertyNameForPropertyNameNode(n2.name));
144150
}
@@ -154,52 +160,63 @@ namespace ts.NavigationBar {
154160
});
155161
}
156162

157-
function addTopLevelNodes(nodes: Node[], topLevelNodes: Node[]): void {
158-
nodes = sortNodes(nodes);
159-
163+
// Add nodes in a single "level" of top-level nodes (as in, methods in a class.)
164+
// Nodes in a single "level" are sorted together.
165+
function addTopLevelNodes(nodes: Node[], higherLevel: Node[]): void {
166+
const thisLevel: Node[] = [];
160167
for (const node of nodes) {
161-
switch (node.kind) {
162-
case SyntaxKind.ClassExpression:
163-
case SyntaxKind.ClassDeclaration:
164-
topLevelNodes.push(node);
165-
for (const member of (<ClassDeclaration>node).members) {
166-
if (member.kind === SyntaxKind.MethodDeclaration || member.kind === SyntaxKind.Constructor) {
167-
type FunctionLikeMember = MethodDeclaration | ConstructorDeclaration;
168-
if ((<FunctionLikeMember>member).body) {
169-
// We do not include methods that does not have child functions in it, because of duplications.
170-
if (hasNamedFunctionDeclarations((<Block>(<FunctionLikeMember>member).body).statements)) {
171-
topLevelNodes.push(member);
172-
}
173-
addTopLevelNodes((<Block>(<MethodDeclaration>member).body).statements, topLevelNodes);
168+
addTopLevelNode(node, thisLevel);
169+
}
170+
doSortNodes(thisLevel);
171+
172+
for (const node of thisLevel) {
173+
higherLevel.push(node);
174+
}
175+
}
176+
177+
function addTopLevelNode(node: Node, thisLevel: Node[]): void {
178+
switch (node.kind) {
179+
case SyntaxKind.ClassExpression:
180+
case SyntaxKind.ClassDeclaration:
181+
thisLevel.push(node);
182+
for (const member of (<ClassDeclaration>node).members) {
183+
if (member.kind === SyntaxKind.MethodDeclaration || member.kind === SyntaxKind.Constructor) {
184+
type FunctionLikeMember = MethodDeclaration | ConstructorDeclaration;
185+
if ((<FunctionLikeMember>member).body) {
186+
// We do not include methods that does not have child functions in it, because of duplications.
187+
if (hasNamedFunctionDeclarations((<Block>(<FunctionLikeMember>member).body).statements)) {
188+
thisLevel.push(member);
174189
}
190+
addTopLevelNodes((<Block>(<MethodDeclaration>member).body).statements, thisLevel);
175191
}
176192
}
177-
break;
178-
case SyntaxKind.EnumDeclaration:
179-
case SyntaxKind.InterfaceDeclaration:
180-
case SyntaxKind.TypeAliasDeclaration:
181-
topLevelNodes.push(node);
182-
break;
193+
}
194+
break;
183195

184-
case SyntaxKind.ModuleDeclaration:
185-
let moduleDeclaration = <ModuleDeclaration>node;
186-
topLevelNodes.push(node);
187-
addTopLevelNodes((<Block>getInnermostModule(moduleDeclaration).body).statements, topLevelNodes);
188-
break;
196+
case SyntaxKind.EnumDeclaration:
197+
case SyntaxKind.InterfaceDeclaration:
198+
case SyntaxKind.TypeAliasDeclaration:
199+
thisLevel.push(node);
200+
break;
189201

190-
case SyntaxKind.FunctionDeclaration:
191-
let functionDeclaration = <FunctionLikeDeclaration>node;
192-
if (isTopLevelFunctionDeclaration(functionDeclaration)) {
193-
topLevelNodes.push(node);
194-
addTopLevelNodes((<Block>functionDeclaration.body).statements, topLevelNodes);
195-
}
196-
break;
202+
case SyntaxKind.ModuleDeclaration:
203+
let moduleDeclaration = <ModuleDeclaration>node;
204+
thisLevel.push(node);
205+
addTopLevelNodes((<Block>getInnermostModule(moduleDeclaration).body).statements, thisLevel);
206+
break;
197207

198-
default:
199-
const childrens: Node[] = [];
200-
forEachChild(node, child => { childrens.push(child) });
201-
addTopLevelNodes(childrens, topLevelNodes);
202-
}
208+
case SyntaxKind.FunctionDeclaration:
209+
let functionDeclaration = <FunctionLikeDeclaration>node;
210+
if (isTopLevelFunctionDeclaration(functionDeclaration)) {
211+
thisLevel.push(node);
212+
addTopLevelNodes((<Block>functionDeclaration.body).statements, thisLevel);
213+
}
214+
break;
215+
216+
default:
217+
// Nodes within nested expressions are still sorted as if they were top-level,
218+
// so in this case we recurse with `addTopLevelNode` rather than calling `addTopLevelNodes`.
219+
forEachChild(node, child => addTopLevelNode(child, thisLevel));
203220
}
204221
}
205222

tests/cases/fourslash/fourslash.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ declare namespace FourSlashInterface {
176176
noDocCommentTemplate(): void;
177177

178178
navigationBarCount(count: number): void;
179+
navigationBarIndex(name: string, index: number): void;
179180
navigationBarContains(name: string, kind: string, fileName?: string, parentName?: string, isAdditionalSpan?: boolean, markerPosition?: number): void;
180181
navigationBarChildItem(parent: string, text: string, kind: string): void;
181182
navigationItemsListCount(count: number, searchValue: string, matchKind?: string): void;
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
/// <reference path="fourslash.ts" />
22

3-
////console.log(class A { b() {} });
3+
////console.log(console.log(class Y {}, class X {}), console.log(class B {}, class A {}));
4+
////console.log(class Cls { meth() {} });
45

5-
debug.printNavigationBar();
6-
verify.navigationBarCount(2);
7-
verify.navigationBarContains("A", "class");
8-
verify.navigationBarChildItem("A", "b", "method");
6+
verify.navigationBarCount(6);
7+
verify.navigationBarIndex("A", 0);
8+
verify.navigationBarIndex("B", 1);
9+
verify.navigationBarIndex("Cls", 2);
10+
verify.navigationBarIndex("X", 3);
11+
verify.navigationBarIndex("Y", 4);
12+
13+
verify.navigationBarContains("Cls", "class");
14+
verify.navigationBarChildItem("Cls", "meth", "method");

0 commit comments

Comments
 (0)