Add support for string literals

This commit is contained in:
Andrew Branch 2019-04-10 14:23:25 -07:00
parent f98c00ab9d
commit e62c2333eb
No known key found for this signature in database
GPG key ID: 22CCA4B120C427D2
2 changed files with 138 additions and 15 deletions

View file

@ -2080,8 +2080,9 @@ namespace ts.server {
return map(locations, location => {
const pos = this.getPosition(location, scriptInfo);
let selectionRange: protocol.SelectionRange = { textSpan: fullTextSpan };
const pushSelectionRange = (textSpan: protocol.TextSpan, syntaxKind?: SyntaxKind): void => {
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 (syntaxKind) {
@ -2092,6 +2093,7 @@ namespace ts.server {
// Skip top-level SyntaxList
let current: Node | undefined = sourceFile.getChildAt(0);
let isInTemplateSpan = false;
while (true) {
const children = current && current.getChildren(sourceFile);
if (!children || !children.length) break;
@ -2103,20 +2105,32 @@ namespace ts.server {
current = undefined;
break;
}
// Blocks are effectively redundant with SyntaxLists; dive in without adding to the list
if (isBlock(node)) {
// Blocks are effectively redundant with SyntaxLists.
// TemplateSpans are an unintuitive grouping of two things which
// should be considered independently.
// Dive in without pushing a selection range.
const nodeIsTemplateSpan = isTemplateSpan(node);
const nodeIsTemplateSpanList = prevNode && isTemplateHead(prevNode);
if (isBlock(node) || nodeIsTemplateSpan || nodeIsTemplateSpanList) {
isInTemplateSpan = nodeIsTemplateSpan;
current = node;
break;
}
if (positionBelongsToNode(node, pos, sourceFile)) {
// Synthesize a stop for '${ ... }' since '${' and '}' actually belong to siblings.
if (isInTemplateSpan && nextNode && isTemplateMiddleOrTemplateTail(nextNode)) {
const start = node.getFullStart() - "${".length;
const end = nextNode.getStart() + "}".length;
pushSelectionRange(start, end, node.kind);
}
// Blocks with braces should be selected from brace to brace, non-inclusive
const isBetweenBraces = isSyntaxList(node)
&& prevNode && prevNode.kind === SyntaxKind.OpenBraceToken
&& nextNode && nextNode.kind === SyntaxKind.CloseBraceToken;
const start = isBetweenBraces ? prevNode.getEnd() : node.getStart();
const end = isBetweenBraces ? nextNode.getStart() : node.getEnd();
const textSpan = this.toLocationTextSpan(createTextSpanFromBounds(start, end), scriptInfo);
pushSelectionRange(textSpan, node.kind);
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
@ -2134,17 +2148,19 @@ namespace ts.server {
const closeBraceToken = Debug.assertDefined(node.getLastToken());
Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken);
Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken);
const spanWithoutBraces = this.toLocationTextSpan(createTextSpanFromBounds(
openBraceToken.getEnd(),
closeBraceToken.getStart(),
), scriptInfo);
const spanWithoutBracesOrTrivia = this.toLocationTextSpan(createTextSpanFromBounds(
firstNonBraceToken.getStart(),
closeBraceToken.getFullStart(),
), scriptInfo);
pushSelectionRange(spanWithoutBraces);
pushSelectionRange(spanWithoutBracesOrTrivia);
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.
if (isStringLiteral(node) || isTemplateLiteral(node)) {
pushSelectionRange(start + 1, end - 1);
}
// If weve made it here, weve already used `isInTemplateSpan` as much as we need
isInTemplateSpan = false;
current = node;
break;
}

View file

@ -194,5 +194,112 @@ type X<T, P> = IsExactlyAny<P> extends true ? T : ({ [K in keyof P]: IsExactlyAn
end: { line: 2, offset: 184 } } } } } } } } } } } } } },
]);
});
it.skip("works for object types", () => {
const getSelectionRange = setup("/file.js", `
type X = {
foo?: string;
readonly bar: number;
}`);
const locations = getSelectionRange([
{
line: 3,
offset: 5,
},
{
line: 4,
offset: 5,
},
{
line: 4,
offset: 14,
},
]);
const allMembersUp: protocol.SelectionRange = {
textSpan: { // all members + whitespace (just inside braces)
start: { line: 2, offset: 11 },
end: { line: 5, offset: 1 } },
parent: {
textSpan: { // add braces
start: { line: 2, offset: 10 },
end: { line: 5, offset: 2 } },
parent: {
textSpan: { // whole TypeAliasDeclaration
start: { line: 2, offset: 1 },
end: { line: 5, offset: 2 } },
parent: {
textSpan: { // SourceFile
start: { line: 1, offset: 1 },
end: { line: 5, offset: 2 } } } } } };
const readonlyBarUp: protocol.SelectionRange = {
textSpan: { // readonly bar
start: { line: 4, offset: 5 },
end: { line: 4, offset: 17 } },
parent: {
textSpan: { // readonly bar: number;
start: { line: 4, offset: 5 },
end: { line: 4, offset: 26 } },
parent: allMembersUp } };
assert.deepEqual(locations, [
{
textSpan: { // foo
start: { line: 3, offset: 5 },
end: { line: 3, offset: 8 } },
parent: {
textSpan: { // foo?
start: { line: 3, offset: 5 },
end: { line: 3, offset: 9 } },
parent: {
textSpan: { // foo?: string;
start: { line: 3, offset: 5 },
end: { line: 3, offset: 18 } },
parent: allMembersUp } } },
{
textSpan: { // readonly
start: { line: 4, offset: 5 },
end: { line: 4, offset: 13 } },
parent: readonlyBarUp },
{
textSpan: { // bar
start: { line: 4, offset: 14 },
end: { line: 4, offset: 17 } },
parent: readonlyBarUp },
]);
});
it("works for string literals and template strings", () => {
// tslint:disable-next-line:no-invalid-template-strings
const getSelectionRange = setup("/file.ts", "`a b ${\n 'c'\n} d`");
const locations = getSelectionRange([{ line: 2, offset: 4 }]);
assert.deepEqual(locations, [
{
textSpan: { // c
start: { line: 2, offset: 4 },
end: { line: 2, offset: 5 } },
parent: {
textSpan: { // 'c'
start: { line: 2, offset: 3 },
end: { line: 2, offset: 6 } },
// parent: {
// textSpan: { // just inside braces
// start: { line: 1, offset: 8 },
// end: { line: 3, offset: 1 } },
parent: {
textSpan: { // whole TemplateSpan: ${ ... }
start: { line: 1, offset: 6 },
end: { line: 3, offset: 2 } },
parent: {
textSpan: { // whole template string without backticks
start: { line: 1, offset: 2 },
end: { line: 3, offset: 4 } },
parent: {
textSpan: { // whole template string
start: { line: 1, offset: 1 },
end: { line: 3, offset: 5 } } } } } } }
]);
});
});
}