Move most logic to separate file

This commit is contained in:
Andrew Branch
2019-04-11 15:15:17 -07:00
parent 0a4ef0f630
commit 61425cb304
4 changed files with 167 additions and 161 deletions

View File

@@ -0,0 +1,116 @@
/* @internal */
namespace ts.server {
const isImport = or(isImportDeclaration, isImportEqualsDeclaration);
export function getSelectionRange(pos: number, sourceFile: SourceFile, pushSelectionRange: (start: number, end: number, kind?: SyntaxKind) => void) {
pushSelectionRange(sourceFile.getFullStart(), sourceFile.getEnd(), SyntaxKind.SourceFile);
// Skip top-level SyntaxList
let parentNode = sourceFile.getChildAt(0);
outer: while (true) {
const children = parentNode.getChildren(sourceFile);
if (!children.length) break;
for (let i = 0; i < children.length; i++) {
const prevNode: Node | undefined = children[i - 1];
const node: Node = children[i];
const nextNode: Node | undefined = children[i + 1];
if (node.getStart(sourceFile) > pos) {
break outer;
}
if (positionBelongsToNode(node, pos, sourceFile)) {
// Blocks are effectively redundant with SyntaxLists.
// TemplateSpans, along with the SyntaxLists containing them,
// are a somewhat unintuitive grouping of things that should be
// considered independently. Dive in without pushing a selection range.
if (isBlock(node) || isTemplateSpan(node) || isTemplateHead(node) || prevNode && isTemplateHead(prevNode)) {
parentNode = node;
break;
}
// Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings.
if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) {
const start = node.getFullStart() - "${".length;
const end = nextNode.getStart() + "}".length;
pushSelectionRange(start, end, node.kind);
}
// Synthesize a stop for group of adjacent imports
else if (isImport(node)) {
const [firstImportIndex, lastImportIndex] = getGroupBounds(children, i, isImport);
pushSelectionRange(
children[firstImportIndex].getStart(),
children[lastImportIndex].getEnd());
}
// Blocks with braces on separate lines should be selected from brace to brace,
// including whitespace but not including the braces themselves.
const isBetweenMultiLineBraces = isSyntaxList(node)
&& prevNode && prevNode.kind === SyntaxKind.OpenBraceToken
&& nextNode && nextNode.kind === SyntaxKind.CloseBraceToken
&& !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile);
const start = isBetweenMultiLineBraces ? prevNode.getEnd() : node.getStart();
const end = isBetweenMultiLineBraces ? nextNode.getStart() : node.getEnd();
pushSelectionRange(start, end, node.kind);
// Mapped types _look_ like ObjectTypes with a single member,
// but in fact dont contain a SyntaxList or a node containing
// the “key/value” pair like ObjectTypes do, but it seems intuitive
// that the selection would snap to those points. The philosophy
// of choosing a selection range is not so much about what the
// syntax currently _is_ as what the syntax might easily become
// if the user is making a selection; e.g., we synthesize a selection
// around the “key/value” pair not because theres a node there, but
// because it allows the mapped type to become an object type with a
// few keystrokes.
if (isMappedTypeNode(node)) {
const openBraceToken = Debug.assertDefined(node.getFirstToken());
const firstNonBraceToken = Debug.assertDefined(node.getChildAt(1));
const closeBraceToken = Debug.assertDefined(node.getLastToken());
Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken);
Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken);
const spanWithoutBraces = [openBraceToken.getEnd(), closeBraceToken.getStart()] as const;
const spanWithoutBracesOrTrivia = [firstNonBraceToken.getStart(), closeBraceToken.getFullStart()] as const;
if (!positionsAreOnSameLine(openBraceToken.getStart(), closeBraceToken.getEnd(), sourceFile)) {
pushSelectionRange(...spanWithoutBraces);
}
pushSelectionRange(...spanWithoutBracesOrTrivia);
}
// String literals should have a stop both inside and outside their quotes.
else if (isStringLiteral(node) || isTemplateLiteral(node)) {
pushSelectionRange(start + 1, end - 1);
}
parentNode = node;
break;
}
}
}
}
function getGroupBounds<T>(array: ArrayLike<T>, index: number, predicate: (element: T) => boolean): [number, number] {
let first = index;
let last = index;
let i = index;
while (i > 0) {
const element = array[--i];
if (predicate(element)) {
first = i;
}
else {
break;
}
}
i = index;
while (i < array.length - 1) {
const element = array[++i];
if (predicate(element)) {
last = i;
}
else {
break;
}
}
return [first, last];
}
}

View File

@@ -112,32 +112,6 @@ namespace ts.server {
return edits.every(edit => textSpanEnd(edit.span) < pos);
}
function getGroupBounds<T>(array: ArrayLike<T>, index: number, predicate: (element: T) => boolean): [number, number] {
let first = index;
let last = index;
let i = index;
while (i > 0) {
const element = array[--i];
if (predicate(element)) {
first = i;
}
else {
break;
}
}
i = index;
while (i < array.length - 1) {
const element = array[++i];
if (predicate(element)) {
last = i;
}
else {
break;
}
}
return [first, last];
}
// CommandNames used to be exposed before TS 2.4 as a namespace
// In TS 2.4 we switched to an enum, keep this for backward compatibility
// The var assignment ensures that even though CommandTypes are a const enum
@@ -2097,107 +2071,26 @@ namespace ts.server {
const { locations } = args;
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const isImport = or(isImportDeclaration, isImportEqualsDeclaration);
const sourceFile = languageService.getNonBoundSourceFile(file);
const scriptInfo = Debug.assertDefined(this.projectService.getScriptInfo(file));
const fullTextSpan = this.toLocationTextSpan(
createTextSpanFromBounds(sourceFile.getFullStart(), sourceFile.getEnd()),
scriptInfo);
return map(locations, location => {
const pos = this.getPosition(location, scriptInfo);
let selectionRange: protocol.SelectionRange = { textSpan: fullTextSpan };
let selectionRange: protocol.SelectionRange | undefined;
const pushSelectionRange = (start: number, end: number, syntaxKind?: SyntaxKind): void => {
// Skip ranges that are identical to the parent
const textSpan = this.toLocationTextSpan(createTextSpanFromBounds(start, end), scriptInfo);
if (!this.locationTextSpansAreEqual(textSpan, selectionRange.textSpan)) {
selectionRange = { textSpan, parent: selectionRange };
if (!selectionRange || !this.locationTextSpansAreEqual(textSpan, selectionRange.textSpan)) {
selectionRange = { textSpan, ...selectionRange && { parent: selectionRange } };
if (syntaxKind) {
Object.defineProperty(selectionRange, "__debugKind", { value: formatSyntaxKind(syntaxKind) });
}
}
};
// Skip top-level SyntaxList
let parentNode = sourceFile.getChildAt(0);
outer: while (true) {
const children = parentNode.getChildren(sourceFile);
if (!children.length) break;
for (let i = 0; i < children.length; i++) {
const prevNode: Node | undefined = children[i - 1];
const node: Node = children[i];
const nextNode: Node | undefined = children[i + 1];
if (node.getStart(sourceFile) > pos) {
break outer;
}
if (positionBelongsToNode(node, pos, sourceFile)) {
// Blocks are effectively redundant with SyntaxLists.
// TemplateSpans, along with the SyntaxLists containing them,
// are a somewhat unintuitive grouping of things that should be
// considered independently. Dive in without pushing a selection range.
if (isBlock(node) || isTemplateSpan(node) || isTemplateHead(node) || prevNode && isTemplateHead(prevNode)) {
parentNode = node;
break;
}
// Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings.
if (isTemplateSpan(parentNode) && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) {
const start = node.getFullStart() - "${".length;
const end = nextNode.getStart() + "}".length;
pushSelectionRange(start, end, node.kind);
}
// Synthesize a stop for group of adjacent imports
else if (isImport(node)) {
const [firstImportIndex, lastImportIndex] = getGroupBounds(children, i, isImport);
pushSelectionRange(
children[firstImportIndex].getStart(),
children[lastImportIndex].getEnd());
}
// Blocks with braces on separate lines should be selected from brace to brace,
// including whitespace but not including the braces themselves.
const isBetweenMultiLineBraces = isSyntaxList(node)
&& prevNode && prevNode.kind === SyntaxKind.OpenBraceToken
&& nextNode && nextNode.kind === SyntaxKind.CloseBraceToken
&& !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile);
const start = isBetweenMultiLineBraces ? prevNode.getEnd() : node.getStart();
const end = isBetweenMultiLineBraces ? nextNode.getStart() : node.getEnd();
pushSelectionRange(start, end, node.kind);
// Mapped types _look_ like ObjectTypes with a single member,
// but in fact dont contain a SyntaxList or a node containing
// the “key/value” pair like ObjectTypes do, but it seems intuitive
// that the selection would snap to those points. The philosophy
// of choosing a selection range is not so much about what the
// syntax currently _is_ as what the syntax might easily become
// if the user is making a selection; e.g., we synthesize a selection
// around the “key/value” pair not because theres a node there, but
// because it allows the mapped type to become an object type with a
// few keystrokes.
if (isMappedTypeNode(node)) {
const openBraceToken = Debug.assertDefined(node.getFirstToken());
const firstNonBraceToken = Debug.assertDefined(node.getChildAt(1));
const closeBraceToken = Debug.assertDefined(node.getLastToken());
Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken);
Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken);
const spanWithoutBraces = [openBraceToken.getEnd(), closeBraceToken.getStart()] as const;
const spanWithoutBracesOrTrivia = [firstNonBraceToken.getStart(), closeBraceToken.getFullStart()] as const;
pushSelectionRange(...spanWithoutBraces);
pushSelectionRange(...spanWithoutBracesOrTrivia);
}
// String literals should have a stop both inside and outside their quotes.
else if (isStringLiteral(node) || isTemplateLiteral(node)) {
pushSelectionRange(start + 1, end - 1);
}
parentNode = node;
break;
}
}
}
return selectionRange;
getSelectionRange(pos, sourceFile, pushSelectionRange);
return selectionRange!;
});
}

View File

@@ -1,27 +1,28 @@
{
"extends": "../tsconfig-base",
"compilerOptions": {
"removeComments": false,
"outFile": "../../built/local/server.js",
"preserveConstEnums": true,
"types": [
"node"
]
},
"references": [
{ "path": "../compiler" },
{ "path": "../jsTyping" },
{ "path": "../services" }
],
"files": [
"types.ts",
"utilities.ts",
"protocol.ts",
"scriptInfo.ts",
"typingsCache.ts",
"project.ts",
"editorServices.ts",
"session.ts",
"scriptVersionCache.ts"
]
}
{
"extends": "../tsconfig-base",
"compilerOptions": {
"removeComments": false,
"outFile": "../../built/local/server.js",
"preserveConstEnums": true,
"types": [
"node"
]
},
"references": [
{ "path": "../compiler" },
{ "path": "../jsTyping" },
{ "path": "../services" }
],
"files": [
"types.ts",
"utilities.ts",
"protocol.ts",
"scriptInfo.ts",
"typingsCache.ts",
"project.ts",
"editorServices.ts",
"selectionRange.ts",
"session.ts",
"scriptVersionCache.ts"
]
}

View File

@@ -164,38 +164,34 @@ type X<T, P> = IsExactlyAny<P> extends true ? T : ({ [K in keyof P]: IsExactlyAn
textSpan: { // [K in keyof P]: IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[K] : P[K];
start: { line: 2, offset: 54 },
end: { line: 2, offset: 143 } },
parent: { // same as above + whitespace
textSpan: {
start: { line: 2, offset: 53 },
end: { line: 2, offset: 144 } },
parent: {
textSpan: { // MappedType: same as above + braces
start: { line: 2, offset: 52 },
end: { line: 2, offset: 145 } },
parent: {
textSpan: { // MappedType: same as above + braces
textSpan: { // IntersectionType: { [K in keyof P]: ... } & Pick<T, Exclude<keyof T, keyof P>>
start: { line: 2, offset: 52 },
end: { line: 2, offset: 145 } },
end: { line: 2, offset: 182 } },
parent: {
textSpan: { // IntersectionType: { [K in keyof P]: ... } & Pick<T, Exclude<keyof T, keyof P>>
start: { line: 2, offset: 52 },
end: { line: 2, offset: 182 } },
textSpan: { // same as above + parens
start: { line: 2, offset: 51 },
end: { line: 2, offset: 183 } },
parent: {
textSpan: { // same as above + parens
start: { line: 2, offset: 51 },
textSpan: { // Whole TypeNode of TypeAliasDeclaration
start: { line: 2, offset: 16 },
end: { line: 2, offset: 183 } },
parent: {
textSpan: { // Whole TypeNode of TypeAliasDeclaration
start: { line: 2, offset: 16 },
textSpan: { // Whole TypeAliasDeclaration
start: { line: 2, offset: 1 },
end: { line: 2, offset: 183 } },
parent: {
textSpan: { // Whole TypeAliasDeclaration
start: { line: 2, offset: 1 },
end: { line: 2, offset: 183 } },
parent: {
textSpan: { // SourceFile
start: { line: 1, offset: 1 },
end: { line: 2, offset: 184 } } } } } } } } } } } } } },
textSpan: { // SourceFile
start: { line: 1, offset: 1 },
end: { line: 2, offset: 184 } } } } } } } } } } } } },
]);
});
it.skip("works for object types", () => {
it("works for object types", () => {
const getSelectionRange = setup("/file.js", `
type X = {
foo?: string;