Add support for string literals
This commit is contained in:
parent
f98c00ab9d
commit
e62c2333eb
2 changed files with 138 additions and 15 deletions
|
@ -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 don’t 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 we’ve made it here, we’ve already used `isInTemplateSpan` as much as we need
|
||||
isInTemplateSpan = false;
|
||||
current = node;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -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 } } } } } } }
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue