mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into cacheUnnormalizedIntersections
This commit is contained in:
+22
-16
@@ -2581,7 +2581,7 @@ namespace ts {
|
||||
// Fix up parent pointers since we're going to use these nodes before we bind into them
|
||||
node.left.parent = node;
|
||||
node.right.parent = node;
|
||||
if (isIdentifier(lhs.expression) && container === file && isNameOfExportsOrModuleExportsAliasDeclaration(file, lhs.expression)) {
|
||||
if (isIdentifier(lhs.expression) && container === file && isExportsOrModuleExportsOrAlias(file, lhs.expression)) {
|
||||
// This can be an alias for the 'exports' or 'module.exports' names, e.g.
|
||||
// var util = module.exports;
|
||||
// util.property = function ...
|
||||
@@ -2975,21 +2975,27 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean {
|
||||
return isExportsIdentifier(node) ||
|
||||
isModuleExportsPropertyAccessExpression(node) ||
|
||||
isIdentifier(node) && isNameOfExportsOrModuleExportsAliasDeclaration(sourceFile, node);
|
||||
}
|
||||
|
||||
function isNameOfExportsOrModuleExportsAliasDeclaration(sourceFile: SourceFile, node: Identifier): boolean {
|
||||
const symbol = lookupSymbolForNameWorker(sourceFile, node.escapedText);
|
||||
return !!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) &&
|
||||
!!symbol.valueDeclaration.initializer && isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, symbol.valueDeclaration.initializer);
|
||||
}
|
||||
|
||||
function isExportsOrModuleExportsOrAliasOrAssignment(sourceFile: SourceFile, node: Expression): boolean {
|
||||
return isExportsOrModuleExportsOrAlias(sourceFile, node) ||
|
||||
(isAssignmentExpression(node, /*excludeCompoundAssignment*/ true) && (
|
||||
isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, node.left) || isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, node.right)));
|
||||
let i = 0;
|
||||
const q = [node];
|
||||
while (q.length && i < 100) {
|
||||
i++;
|
||||
node = q.shift()!;
|
||||
if (isExportsIdentifier(node) || isModuleExportsPropertyAccessExpression(node)) {
|
||||
return true;
|
||||
}
|
||||
else if (isIdentifier(node)) {
|
||||
const symbol = lookupSymbolForNameWorker(sourceFile, node.escapedText);
|
||||
if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) {
|
||||
const init = symbol.valueDeclaration.initializer;
|
||||
q.push(init);
|
||||
if (isAssignmentExpression(init, /*excludeCompoundAssignment*/ true)) {
|
||||
q.push(init.left);
|
||||
q.push(init.right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function lookupSymbolForNameWorker(container: Node, name: __String): Symbol | undefined {
|
||||
|
||||
@@ -424,6 +424,10 @@ namespace ts.server {
|
||||
return renameInfo;
|
||||
}
|
||||
|
||||
getSmartSelectionRange() {
|
||||
return notImplemented();
|
||||
}
|
||||
|
||||
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] {
|
||||
if (!this.lastRenameEntry ||
|
||||
this.lastRenameEntry.inputs.fileName !== fileName ||
|
||||
|
||||
@@ -1417,12 +1417,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
}
|
||||
|
||||
public baselineCurrentFileBreakpointLocations() {
|
||||
let baselineFile = this.testData.globalOptions[MetadataOptionNames.baselineFile];
|
||||
if (!baselineFile) {
|
||||
baselineFile = this.activeFile.fileName.replace(this.basePath + "/breakpointValidation", "bpSpan");
|
||||
baselineFile = baselineFile.replace(ts.Extension.Ts, ".baseline");
|
||||
|
||||
}
|
||||
const baselineFile = this.getBaselineFileName().replace("breakpointValidation", "bpSpan");
|
||||
Harness.Baseline.runBaseline(baselineFile, this.baselineCurrentFileLocations(pos => this.getBreakpointStatementLocation(pos)!));
|
||||
}
|
||||
|
||||
@@ -1497,8 +1492,7 @@ Actual: ${stringify(fullActual)}`);
|
||||
}
|
||||
|
||||
public baselineQuickInfo() {
|
||||
const baselineFile = this.testData.globalOptions[MetadataOptionNames.baselineFile] ||
|
||||
ts.getBaseFileName(this.activeFile.fileName).replace(ts.Extension.Ts, ".baseline");
|
||||
const baselineFile = this.getBaselineFileName();
|
||||
Harness.Baseline.runBaseline(
|
||||
baselineFile,
|
||||
stringify(
|
||||
@@ -1508,6 +1502,39 @@ Actual: ${stringify(fullActual)}`);
|
||||
}))));
|
||||
}
|
||||
|
||||
public baselineSmartSelection() {
|
||||
const n = "\n";
|
||||
const baselineFile = this.getBaselineFileName();
|
||||
const markers = this.getMarkers();
|
||||
const fileContent = this.activeFile.content;
|
||||
const text = markers.map(marker => {
|
||||
const baselineContent = [fileContent.slice(0, marker.position) + "/**/" + fileContent.slice(marker.position) + n];
|
||||
let selectionRange: ts.SelectionRange | undefined = this.languageService.getSmartSelectionRange(this.activeFile.fileName, marker.position);
|
||||
while (selectionRange) {
|
||||
const { textSpan } = selectionRange;
|
||||
let masked = Array.from(fileContent).map((char, index) => {
|
||||
const charCode = char.charCodeAt(0);
|
||||
if (index >= textSpan.start && index < ts.textSpanEnd(textSpan)) {
|
||||
return char === " " ? "•" : ts.isLineBreak(charCode) ? `↲${n}` : char;
|
||||
}
|
||||
return ts.isLineBreak(charCode) ? char : " ";
|
||||
}).join("");
|
||||
masked = masked.replace(/^\s*$\r?\n?/gm, ""); // Remove blank lines
|
||||
const isRealCharacter = (char: string) => char !== "•" && char !== "↲" && !ts.isWhiteSpaceLike(char.charCodeAt(0));
|
||||
const leadingWidth = Array.from(masked).findIndex(isRealCharacter);
|
||||
const trailingWidth = ts.findLastIndex(Array.from(masked), isRealCharacter);
|
||||
masked = masked.slice(0, leadingWidth)
|
||||
+ masked.slice(leadingWidth, trailingWidth).replace(/•/g, " ").replace(/↲/g, "")
|
||||
+ masked.slice(trailingWidth);
|
||||
baselineContent.push(masked);
|
||||
selectionRange = selectionRange.parent;
|
||||
}
|
||||
return baselineContent.join(fileContent.includes("\n") ? n + n : n);
|
||||
}).join(n.repeat(2) + "=".repeat(80) + n.repeat(2));
|
||||
|
||||
Harness.Baseline.runBaseline(baselineFile, text);
|
||||
}
|
||||
|
||||
public printBreakpointLocation(pos: number) {
|
||||
Harness.IO.log("\n**Pos: " + pos + " " + this.spanInfoToString(this.getBreakpointStatementLocation(pos)!, " "));
|
||||
}
|
||||
@@ -1562,6 +1589,11 @@ Actual: ${stringify(fullActual)}`);
|
||||
Harness.IO.log(stringify(help.items[help.selectedItemIndex]));
|
||||
}
|
||||
|
||||
private getBaselineFileName() {
|
||||
return this.testData.globalOptions[MetadataOptionNames.baselineFile] ||
|
||||
ts.getBaseFileName(this.activeFile.fileName).replace(ts.Extension.Ts, ".baseline");
|
||||
}
|
||||
|
||||
private getSignatureHelp({ triggerReason }: FourSlashInterface.VerifySignatureHelpOptions): ts.SignatureHelpItems | undefined {
|
||||
return this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition, {
|
||||
triggerReason
|
||||
@@ -3960,6 +3992,10 @@ namespace FourSlashInterface {
|
||||
this.state.baselineQuickInfo();
|
||||
}
|
||||
|
||||
public baselineSmartSelection() {
|
||||
this.state.baselineSmartSelection();
|
||||
}
|
||||
|
||||
public nameOrDottedNameSpanTextIs(text: string) {
|
||||
this.state.verifyCurrentNameOrDottedNameSpanText(text);
|
||||
}
|
||||
|
||||
@@ -472,6 +472,9 @@ namespace Harness.LanguageService {
|
||||
getRenameInfo(fileName: string, position: number, options?: ts.RenameInfoOptions): ts.RenameInfo {
|
||||
return unwrapJSONCallResult(this.shim.getRenameInfo(fileName, position, options));
|
||||
}
|
||||
getSmartSelectionRange(fileName: string, position: number): ts.SelectionRange {
|
||||
return unwrapJSONCallResult(this.shim.getSmartSelectionRange(fileName, position));
|
||||
}
|
||||
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ts.RenameLocation[] {
|
||||
return unwrapJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename));
|
||||
}
|
||||
|
||||
+22
-1
@@ -130,7 +130,10 @@ namespace ts.server.protocol {
|
||||
GetEditsForFileRename = "getEditsForFileRename",
|
||||
/* @internal */
|
||||
GetEditsForFileRenameFull = "getEditsForFileRename-full",
|
||||
ConfigurePlugin = "configurePlugin"
|
||||
ConfigurePlugin = "configurePlugin",
|
||||
SelectionRange = "selectionRange",
|
||||
/* @internal */
|
||||
SelectionRangeFull = "selectionRange-full",
|
||||
|
||||
// NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`.
|
||||
}
|
||||
@@ -1395,6 +1398,24 @@ namespace ts.server.protocol {
|
||||
export interface ConfigurePluginResponse extends Response {
|
||||
}
|
||||
|
||||
export interface SelectionRangeRequest extends FileRequest {
|
||||
command: CommandTypes.SelectionRange;
|
||||
arguments: SelectionRangeRequestArgs;
|
||||
}
|
||||
|
||||
export interface SelectionRangeRequestArgs extends FileRequestArgs {
|
||||
locations: Location[];
|
||||
}
|
||||
|
||||
export interface SelectionRangeResponse extends Response {
|
||||
body?: SelectionRange[];
|
||||
}
|
||||
|
||||
export interface SelectionRange {
|
||||
textSpan: TextSpan;
|
||||
parent?: SelectionRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information found in an "open" request.
|
||||
*/
|
||||
|
||||
+31
-3
@@ -1318,11 +1318,11 @@ namespace ts.server {
|
||||
this.projectService.openClientFileWithNormalizedPath(fileName, fileContent, scriptKind, /*hasMixedContent*/ false, projectRootPath);
|
||||
}
|
||||
|
||||
private getPosition(args: protocol.FileLocationRequestArgs, scriptInfo: ScriptInfo): number {
|
||||
private getPosition(args: protocol.Location & { position?: number }, scriptInfo: ScriptInfo): number {
|
||||
return args.position !== undefined ? args.position : scriptInfo.lineOffsetToPosition(args.line, args.offset);
|
||||
}
|
||||
|
||||
private getPositionInFile(args: protocol.FileLocationRequestArgs, file: NormalizedPath): number {
|
||||
private getPositionInFile(args: protocol.Location & { position?: number }, file: NormalizedPath): number {
|
||||
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
|
||||
return this.getPosition(args, scriptInfo);
|
||||
}
|
||||
@@ -2059,6 +2059,28 @@ namespace ts.server {
|
||||
this.projectService.configurePlugin(args);
|
||||
}
|
||||
|
||||
private getSmartSelectionRange(args: protocol.SelectionRangeRequestArgs, simplifiedResult: boolean) {
|
||||
const { locations } = args;
|
||||
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
|
||||
const scriptInfo = Debug.assertDefined(this.projectService.getScriptInfo(file));
|
||||
|
||||
return map(locations, location => {
|
||||
const pos = this.getPosition(location, scriptInfo);
|
||||
const selectionRange = languageService.getSmartSelectionRange(file, pos);
|
||||
return simplifiedResult ? this.mapSelectionRange(selectionRange, scriptInfo) : selectionRange;
|
||||
});
|
||||
}
|
||||
|
||||
private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange {
|
||||
const result: protocol.SelectionRange = {
|
||||
textSpan: this.toLocationTextSpan(selectionRange.textSpan, scriptInfo),
|
||||
};
|
||||
if (selectionRange.parent) {
|
||||
result.parent = this.mapSelectionRange(selectionRange.parent, scriptInfo);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string) {
|
||||
const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
|
||||
return normalizePath(name);
|
||||
@@ -2414,7 +2436,13 @@ namespace ts.server {
|
||||
this.configurePlugin(request.arguments);
|
||||
this.doOutput(/*info*/ undefined, CommandNames.ConfigurePlugin, request.seq, /*success*/ true);
|
||||
return this.notRequired();
|
||||
}
|
||||
},
|
||||
[CommandNames.SelectionRange]: (request: protocol.SelectionRangeRequest) => {
|
||||
return this.requiredResponse(this.getSmartSelectionRange(request.arguments, /*simplifiedResult*/ true));
|
||||
},
|
||||
[CommandNames.SelectionRangeFull]: (request: protocol.SelectionRangeRequest) => {
|
||||
return this.requiredResponse(this.getSmartSelectionRange(request.arguments, /*simplifiedResult*/ false));
|
||||
},
|
||||
});
|
||||
|
||||
public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {
|
||||
|
||||
@@ -2080,6 +2080,10 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
|
||||
function getSmartSelectionRange(fileName: string, position: number): SelectionRange {
|
||||
return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName));
|
||||
}
|
||||
|
||||
function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions): ApplicableRefactorInfo[] {
|
||||
synchronizeHostData();
|
||||
const file = getValidSourceFile(fileName);
|
||||
@@ -2127,6 +2131,7 @@ namespace ts {
|
||||
getBreakpointStatementAtPosition,
|
||||
getNavigateToItems,
|
||||
getRenameInfo,
|
||||
getSmartSelectionRange,
|
||||
findRenameLocations,
|
||||
getNavigationBarItems,
|
||||
getNavigationTree,
|
||||
|
||||
@@ -165,6 +165,7 @@ namespace ts {
|
||||
* { canRename: boolean, localizedErrorMessage: string, displayName: string, fullDisplayName: string, kind: string, kindModifiers: string, triggerSpan: { start; length } }
|
||||
*/
|
||||
getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): string;
|
||||
getSmartSelectionRange(fileName: string, position: number): string;
|
||||
|
||||
/**
|
||||
* Returns a JSON-encoded value of the type:
|
||||
@@ -838,6 +839,13 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
public getSmartSelectionRange(fileName: string, position: number): string {
|
||||
return this.forwardJSONCall(
|
||||
`getSmartSelectionRange('${fileName}', ${position})`,
|
||||
() => this.languageService.getSmartSelectionRange(fileName, position)
|
||||
);
|
||||
}
|
||||
|
||||
public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string {
|
||||
return this.forwardJSONCall(
|
||||
`findRenameLocations('${fileName}', ${position}, ${findInStrings}, ${findInComments}, ${providePrefixAndSuffixTextForRename})`,
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
/* @internal */
|
||||
namespace ts.SmartSelectionRange {
|
||||
export function getSmartSelectionRange(pos: number, sourceFile: SourceFile): SelectionRange {
|
||||
let selectionRange: SelectionRange = {
|
||||
textSpan: createTextSpanFromBounds(sourceFile.getFullStart(), sourceFile.getEnd())
|
||||
};
|
||||
|
||||
let parentNode: Node = sourceFile;
|
||||
outer: while (true) {
|
||||
const children = getSelectionChildren(parentNode);
|
||||
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 (positionShouldSnapToNode(pos, node, nextNode)) {
|
||||
// 1. Blocks are effectively redundant with SyntaxLists.
|
||||
// 2. TemplateSpans, along with the SyntaxLists containing them, are a somewhat unintuitive grouping
|
||||
// of things that should be considered independently.
|
||||
// 3. A VariableStatement’s children are just a VaraiableDeclarationList and a semicolon.
|
||||
// 4. A lone VariableDeclaration in a VaraibleDeclaration feels redundant with the VariableStatement.
|
||||
//
|
||||
// Dive in without pushing a selection range.
|
||||
if (isBlock(node)
|
||||
|| isTemplateSpan(node) || isTemplateHead(node)
|
||||
|| prevNode && isTemplateHead(prevNode)
|
||||
|| isVariableDeclarationList(node) && isVariableStatement(parentNode)
|
||||
|| isSyntaxList(node) && isVariableDeclarationList(parentNode)
|
||||
|| isVariableDeclaration(node) && isSyntaxList(parentNode) && children.length === 1) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Blocks with braces, brackets, parens, or JSX tags on separate lines should be
|
||||
// selected from open to close, including whitespace but not including the braces/etc. themselves.
|
||||
const isBetweenMultiLineBookends = isSyntaxList(node)
|
||||
&& isListOpener(prevNode)
|
||||
&& isListCloser(nextNode)
|
||||
&& !positionsAreOnSameLine(prevNode.getStart(), nextNode.getStart(), sourceFile);
|
||||
const jsDocCommentStart = hasJSDocNodes(node) && node.jsDoc![0].getStart();
|
||||
const start = isBetweenMultiLineBookends ? prevNode.getEnd() : node.getStart();
|
||||
const end = isBetweenMultiLineBookends ? nextNode.getStart() : node.getEnd();
|
||||
if (isNumber(jsDocCommentStart)) {
|
||||
pushSelectionRange(jsDocCommentStart, end);
|
||||
}
|
||||
pushSelectionRange(start, end);
|
||||
|
||||
// String literals should have a stop both inside and outside their quotes.
|
||||
if (isStringLiteral(node) || isTemplateLiteral(node)) {
|
||||
pushSelectionRange(start + 1, end - 1);
|
||||
}
|
||||
|
||||
parentNode = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selectionRange;
|
||||
|
||||
function pushSelectionRange(start: number, end: number): void {
|
||||
// Skip empty ranges
|
||||
if (start !== end) {
|
||||
// Skip ranges that are identical to the parent
|
||||
const textSpan = createTextSpanFromBounds(start, end);
|
||||
if (!selectionRange || !textSpansEqual(textSpan, selectionRange.textSpan)) {
|
||||
selectionRange = { textSpan, ...selectionRange && { parent: selectionRange } };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `ts.positionBelongsToNode`, except positions immediately after nodes
|
||||
* count too, unless that position belongs to the next node. In effect, makes
|
||||
* selections able to snap to preceding tokens when the cursor is on the tail
|
||||
* end of them with only whitespace ahead.
|
||||
* @param pos The position to check.
|
||||
* @param node The candidate node to snap to.
|
||||
* @param nextNode The next sibling node in the tree.
|
||||
* @param sourceFile The source file containing the nodes.
|
||||
*/
|
||||
function positionShouldSnapToNode(pos: number, node: Node, nextNode: Node | undefined) {
|
||||
// Can’t use 'ts.positionBelongsToNode()' here because it cleverly accounts
|
||||
// for missing nodes, which can’t really be considered when deciding what
|
||||
// to select.
|
||||
Debug.assert(node.pos <= pos);
|
||||
if (pos < node.end) {
|
||||
return true;
|
||||
}
|
||||
const nodeEnd = node.getEnd();
|
||||
const nextNodeStart = nextNode && nextNode.getStart();
|
||||
if (nodeEnd === pos) {
|
||||
return pos !== nextNodeStart;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const isImport = or(isImportDeclaration, isImportEqualsDeclaration);
|
||||
|
||||
/**
|
||||
* Gets the children of a node to be considered for selection ranging,
|
||||
* transforming them into an artificial tree according to their intuitive
|
||||
* grouping where no grouping actually exists in the parse tree. For example,
|
||||
* top-level imports are grouped into their own SyntaxList so they can be
|
||||
* selected all together, even though in the AST they’re just siblings of each
|
||||
* other as well as of other top-level statements and declarations.
|
||||
*/
|
||||
function getSelectionChildren(node: Node): ReadonlyArray<Node> {
|
||||
// Group top-level imports
|
||||
if (isSourceFile(node)) {
|
||||
return groupChildren(node.getChildAt(0).getChildren(), isImport);
|
||||
}
|
||||
|
||||
// Mapped types _look_ like ObjectTypes with a single member,
|
||||
// but in fact don’t 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 there’s a node there, but
|
||||
// because it allows the mapped type to become an object type with a
|
||||
// few keystrokes.
|
||||
if (isMappedTypeNode(node)) {
|
||||
const [openBraceToken, ...children] = node.getChildren();
|
||||
const closeBraceToken = Debug.assertDefined(children.pop());
|
||||
Debug.assertEqual(openBraceToken.kind, SyntaxKind.OpenBraceToken);
|
||||
Debug.assertEqual(closeBraceToken.kind, SyntaxKind.CloseBraceToken);
|
||||
// Group `-/+readonly` and `-/+?`
|
||||
const groupedWithPlusMinusTokens = groupChildren(children, child =>
|
||||
child === node.readonlyToken || child.kind === SyntaxKind.ReadonlyKeyword ||
|
||||
child === node.questionToken || child.kind === SyntaxKind.QuestionToken);
|
||||
// Group type parameter with surrounding brackets
|
||||
const groupedWithBrackets = groupChildren(groupedWithPlusMinusTokens, ({ kind }) =>
|
||||
kind === SyntaxKind.OpenBracketToken ||
|
||||
kind === SyntaxKind.TypeParameter ||
|
||||
kind === SyntaxKind.CloseBracketToken
|
||||
);
|
||||
return [
|
||||
openBraceToken,
|
||||
// Pivot on `:`
|
||||
createSyntaxList(splitChildren(groupedWithBrackets, ({ kind }) => kind === SyntaxKind.ColonToken)),
|
||||
closeBraceToken,
|
||||
];
|
||||
}
|
||||
|
||||
// Group modifiers and property name, then pivot on `:`.
|
||||
if (isPropertySignature(node)) {
|
||||
const children = groupChildren(node.getChildren(), child =>
|
||||
child === node.name || contains(node.modifiers, child));
|
||||
return splitChildren(children, ({ kind }) => kind === SyntaxKind.ColonToken);
|
||||
}
|
||||
|
||||
// Group the parameter name with its `...`, then that group with its `?`, then pivot on `=`.
|
||||
if (isParameter(node)) {
|
||||
const groupedDotDotDotAndName = groupChildren(node.getChildren(), child =>
|
||||
child === node.dotDotDotToken || child === node.name);
|
||||
const groupedWithQuestionToken = groupChildren(groupedDotDotDotAndName, child =>
|
||||
child === groupedDotDotDotAndName[0] || child === node.questionToken);
|
||||
return splitChildren(groupedWithQuestionToken, ({ kind }) => kind === SyntaxKind.EqualsToken);
|
||||
}
|
||||
|
||||
// Pivot on '='
|
||||
if (isBindingElement(node)) {
|
||||
return splitChildren(node.getChildren(), ({ kind }) => kind === SyntaxKind.EqualsToken);
|
||||
}
|
||||
|
||||
return node.getChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups sibling nodes together into their own SyntaxList if they
|
||||
* a) are adjacent, AND b) match a predicate function.
|
||||
*/
|
||||
function groupChildren(children: Node[], groupOn: (child: Node) => boolean): Node[] {
|
||||
const result: Node[] = [];
|
||||
let group: Node[] | undefined;
|
||||
for (const child of children) {
|
||||
if (groupOn(child)) {
|
||||
group = group || [];
|
||||
group.push(child);
|
||||
}
|
||||
else {
|
||||
if (group) {
|
||||
result.push(createSyntaxList(group));
|
||||
group = undefined;
|
||||
}
|
||||
result.push(child);
|
||||
}
|
||||
}
|
||||
if (group) {
|
||||
result.push(createSyntaxList(group));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits sibling nodes into up to four partitions:
|
||||
* 1) everything left of the first node matched by `pivotOn`,
|
||||
* 2) the first node matched by `pivotOn`,
|
||||
* 3) everything right of the first node matched by `pivotOn`,
|
||||
* 4) a trailing semicolon, if `separateTrailingSemicolon` is enabled.
|
||||
* The left and right groups, if not empty, will each be grouped into their own containing SyntaxList.
|
||||
* @param children The sibling nodes to split.
|
||||
* @param pivotOn The predicate function to match the node to be the pivot. The first node that matches
|
||||
* the predicate will be used; any others that may match will be included into the right-hand group.
|
||||
* @param separateTrailingSemicolon If the last token is a semicolon, it will be returned as a separate
|
||||
* child rather than be included in the right-hand group.
|
||||
*/
|
||||
function splitChildren(children: Node[], pivotOn: (child: Node) => boolean, separateTrailingSemicolon = true): Node[] {
|
||||
if (children.length < 2) {
|
||||
return children;
|
||||
}
|
||||
const splitTokenIndex = findIndex(children, pivotOn);
|
||||
if (splitTokenIndex === -1) {
|
||||
return children;
|
||||
}
|
||||
const leftChildren = children.slice(0, splitTokenIndex);
|
||||
const splitToken = children[splitTokenIndex];
|
||||
const lastToken = last(children);
|
||||
const separateLastToken = separateTrailingSemicolon && lastToken.kind === SyntaxKind.SemicolonToken;
|
||||
const rightChildren = children.slice(splitTokenIndex + 1, separateLastToken ? children.length - 1 : undefined);
|
||||
const result = compact([
|
||||
leftChildren.length ? createSyntaxList(leftChildren) : undefined,
|
||||
splitToken,
|
||||
rightChildren.length ? createSyntaxList(rightChildren) : undefined,
|
||||
]);
|
||||
return separateLastToken ? result.concat(lastToken) : result;
|
||||
}
|
||||
|
||||
function createSyntaxList(children: Node[]): SyntaxList {
|
||||
Debug.assertGreaterThanOrEqual(children.length, 1);
|
||||
const syntaxList = createNode(SyntaxKind.SyntaxList, children[0].pos, last(children).end) as SyntaxList;
|
||||
syntaxList._children = children;
|
||||
return syntaxList;
|
||||
}
|
||||
|
||||
function isListOpener(token: Node | undefined): token is Node {
|
||||
const kind = token && token.kind;
|
||||
return kind === SyntaxKind.OpenBraceToken
|
||||
|| kind === SyntaxKind.OpenBracketToken
|
||||
|| kind === SyntaxKind.OpenParenToken
|
||||
|| kind === SyntaxKind.JsxOpeningElement;
|
||||
}
|
||||
|
||||
function isListCloser(token: Node | undefined): token is Node {
|
||||
const kind = token && token.kind;
|
||||
return kind === SyntaxKind.CloseBraceToken
|
||||
|| kind === SyntaxKind.CloseBracketToken
|
||||
|| kind === SyntaxKind.CloseParenToken
|
||||
|| kind === SyntaxKind.JsxClosingElement;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
"patternMatcher.ts",
|
||||
"preProcess.ts",
|
||||
"rename.ts",
|
||||
"smartSelection.ts",
|
||||
"signatureHelp.ts",
|
||||
"sourcemaps.ts",
|
||||
"suggestionDiagnostics.ts",
|
||||
|
||||
@@ -296,6 +296,8 @@ namespace ts {
|
||||
getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo;
|
||||
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ReadonlyArray<RenameLocation> | undefined;
|
||||
|
||||
getSmartSelectionRange(fileName: string, position: number): SelectionRange;
|
||||
|
||||
getDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined;
|
||||
getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined;
|
||||
getTypeDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined;
|
||||
@@ -848,6 +850,11 @@ namespace ts {
|
||||
isOptional: boolean;
|
||||
}
|
||||
|
||||
export interface SelectionRange {
|
||||
textSpan: TextSpan;
|
||||
parent?: SelectionRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single signature to show in signature help.
|
||||
* The id is used for subsequent calls into the language service to ask questions about the
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
"unittests/tsserver/reload.ts",
|
||||
"unittests/tsserver/rename.ts",
|
||||
"unittests/tsserver/resolutionCache.ts",
|
||||
"unittests/tsserver/smartSelection.ts",
|
||||
"unittests/tsserver/session.ts",
|
||||
"unittests/tsserver/skipLibCheck.ts",
|
||||
"unittests/tsserver/symLinks.ts",
|
||||
|
||||
@@ -264,6 +264,7 @@ namespace ts.server {
|
||||
CommandNames.OrganizeImportsFull,
|
||||
CommandNames.GetEditsForFileRename,
|
||||
CommandNames.GetEditsForFileRenameFull,
|
||||
CommandNames.SelectionRange,
|
||||
];
|
||||
|
||||
it("should not throw when commands are executed with invalid arguments", () => {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
namespace ts.projectSystem {
|
||||
function setup(fileName: string, content: string) {
|
||||
const file: File = { path: fileName, content };
|
||||
const host = createServerHost([file, libFile]);
|
||||
const session = createSession(host);
|
||||
openFilesForSession([file], session);
|
||||
return function getSmartSelectionRange(locations: protocol.SelectionRangeRequestArgs["locations"]) {
|
||||
return executeSessionRequest<protocol.SelectionRangeRequest, protocol.SelectionRangeResponse>(
|
||||
session,
|
||||
CommandNames.SelectionRange,
|
||||
{ file: fileName, locations });
|
||||
};
|
||||
}
|
||||
|
||||
// More tests in fourslash/smartSelection_*
|
||||
describe("unittests:: tsserver:: smartSelection", () => {
|
||||
it("works for simple JavaScript", () => {
|
||||
const getSmartSelectionRange = setup("/file.js", `
|
||||
class Foo {
|
||||
bar(a, b) {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}`);
|
||||
|
||||
const locations = getSmartSelectionRange([
|
||||
{ line: 4, offset: 13 }, // a === b
|
||||
]);
|
||||
|
||||
assert.deepEqual(locations, [{
|
||||
textSpan: { // a
|
||||
start: { line: 4, offset: 13 },
|
||||
end: { line: 4, offset: 14 } },
|
||||
parent: {
|
||||
textSpan: { // a === b
|
||||
start: { line: 4, offset: 13 },
|
||||
end: { line: 4, offset: 20 } },
|
||||
parent: {
|
||||
textSpan: { // IfStatement
|
||||
start: { line: 4, offset: 9 },
|
||||
end: { line: 6, offset: 10 } },
|
||||
parent: {
|
||||
textSpan: { // SyntaxList + whitespace (body of method)
|
||||
start: { line: 3, offset: 16 },
|
||||
end: { line: 8, offset: 5 } },
|
||||
parent: {
|
||||
textSpan: { // MethodDeclaration
|
||||
start: { line: 3, offset: 5 },
|
||||
end: { line: 8, offset: 6 } },
|
||||
parent: {
|
||||
textSpan: { // SyntaxList + whitespace (body of class)
|
||||
start: { line: 2, offset: 12 },
|
||||
end: { line: 9, offset: 1 } },
|
||||
parent: {
|
||||
textSpan: { // ClassDeclaration
|
||||
start: { line: 2, offset: 1 },
|
||||
end: { line: 9, offset: 2 } },
|
||||
parent: {
|
||||
textSpan: { // SourceFile (all text)
|
||||
start: { line: 1, offset: 1 },
|
||||
end: { line: 9, offset: 2 }, } } } } } } } } }]);
|
||||
});
|
||||
});
|
||||
}
|
||||
+23
-1
@@ -4809,6 +4809,7 @@ declare namespace ts {
|
||||
getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): SignatureHelpItems | undefined;
|
||||
getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo;
|
||||
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ReadonlyArray<RenameLocation> | undefined;
|
||||
getSmartSelectionRange(fileName: string, position: number): SelectionRange;
|
||||
getDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined;
|
||||
getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined;
|
||||
getTypeDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined;
|
||||
@@ -5253,6 +5254,10 @@ declare namespace ts {
|
||||
displayParts: SymbolDisplayPart[];
|
||||
isOptional: boolean;
|
||||
}
|
||||
interface SelectionRange {
|
||||
textSpan: TextSpan;
|
||||
parent?: SelectionRange;
|
||||
}
|
||||
/**
|
||||
* Represents a single signature to show in signature help.
|
||||
* The id is used for subsequent calls into the language service to ask questions about the
|
||||
@@ -5800,7 +5805,8 @@ declare namespace ts.server.protocol {
|
||||
GetEditsForRefactor = "getEditsForRefactor",
|
||||
OrganizeImports = "organizeImports",
|
||||
GetEditsForFileRename = "getEditsForFileRename",
|
||||
ConfigurePlugin = "configurePlugin"
|
||||
ConfigurePlugin = "configurePlugin",
|
||||
SelectionRange = "selectionRange",
|
||||
}
|
||||
/**
|
||||
* A TypeScript Server message
|
||||
@@ -6763,6 +6769,20 @@ declare namespace ts.server.protocol {
|
||||
}
|
||||
interface ConfigurePluginResponse extends Response {
|
||||
}
|
||||
interface SelectionRangeRequest extends FileRequest {
|
||||
command: CommandTypes.SelectionRange;
|
||||
arguments: SelectionRangeRequestArgs;
|
||||
}
|
||||
interface SelectionRangeRequestArgs extends FileRequestArgs {
|
||||
locations: Location[];
|
||||
}
|
||||
interface SelectionRangeResponse extends Response {
|
||||
body?: SelectionRange[];
|
||||
}
|
||||
interface SelectionRange {
|
||||
textSpan: TextSpan;
|
||||
parent?: SelectionRange;
|
||||
}
|
||||
/**
|
||||
* Information found in an "open" request.
|
||||
*/
|
||||
@@ -9039,6 +9059,8 @@ declare namespace ts.server {
|
||||
private getBraceMatching;
|
||||
private getDiagnosticsForProject;
|
||||
private configurePlugin;
|
||||
private getSmartSelectionRange;
|
||||
private mapSelectionRange;
|
||||
getCanonicalFileName(fileName: string): string;
|
||||
exit(): void;
|
||||
private notRequired;
|
||||
|
||||
@@ -4809,6 +4809,7 @@ declare namespace ts {
|
||||
getSignatureHelpItems(fileName: string, position: number, options: SignatureHelpItemsOptions | undefined): SignatureHelpItems | undefined;
|
||||
getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo;
|
||||
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ReadonlyArray<RenameLocation> | undefined;
|
||||
getSmartSelectionRange(fileName: string, position: number): SelectionRange;
|
||||
getDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined;
|
||||
getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan | undefined;
|
||||
getTypeDefinitionAtPosition(fileName: string, position: number): ReadonlyArray<DefinitionInfo> | undefined;
|
||||
@@ -5253,6 +5254,10 @@ declare namespace ts {
|
||||
displayParts: SymbolDisplayPart[];
|
||||
isOptional: boolean;
|
||||
}
|
||||
interface SelectionRange {
|
||||
textSpan: TextSpan;
|
||||
parent?: SelectionRange;
|
||||
}
|
||||
/**
|
||||
* Represents a single signature to show in signature help.
|
||||
* The id is used for subsequent calls into the language service to ask questions about the
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
=== tests/cases/conformance/salsa/loop.js ===
|
||||
var loop1 = loop2;
|
||||
>loop1 : Symbol(loop1, Decl(loop.js, 0, 3))
|
||||
>loop2 : Symbol(loop2, Decl(loop.js, 1, 3))
|
||||
|
||||
var loop2 = loop1;
|
||||
>loop2 : Symbol(loop2, Decl(loop.js, 1, 3))
|
||||
>loop1 : Symbol(loop1, Decl(loop.js, 0, 3))
|
||||
|
||||
module.exports = loop2;
|
||||
>module.exports : Symbol("tests/cases/conformance/salsa/loop", Decl(loop.js, 0, 0))
|
||||
>module : Symbol(export=, Decl(loop.js, 1, 18))
|
||||
>exports : Symbol(export=, Decl(loop.js, 1, 18))
|
||||
>loop2 : Symbol(loop2, Decl(loop.js, 1, 3))
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
=== tests/cases/conformance/salsa/loop.js ===
|
||||
var loop1 = loop2;
|
||||
>loop1 : any
|
||||
>loop2 : any
|
||||
|
||||
var loop2 = loop1;
|
||||
>loop2 : any
|
||||
>loop1 : any
|
||||
|
||||
module.exports = loop2;
|
||||
>module.exports = loop2 : any
|
||||
>module.exports : any
|
||||
>module : { "tests/cases/conformance/salsa/loop": any; }
|
||||
>exports : any
|
||||
>loop2 : any
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Not a JSDoc comment
|
||||
/**
|
||||
* @param {number} x The number to square
|
||||
*/
|
||||
function /**/square(x) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
|
||||
square
|
||||
|
||||
|
||||
function square(x) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x The number to square
|
||||
*/
|
||||
function square(x) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
// Not a JSDoc comment
|
||||
/**
|
||||
* @param {number} x The number to square
|
||||
*/
|
||||
function square(x) {
|
||||
return x * x;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
let/**/ x: string
|
||||
|
||||
let
|
||||
let x: string
|
||||
@@ -0,0 +1,27 @@
|
||||
const { /**/x, y: a, ...zs = {} } = {};
|
||||
|
||||
x
|
||||
x, y: a, ...zs = {}
|
||||
{ x, y: a, ...zs = {} }
|
||||
const { x, y: a, ...zs = {} } = {};
|
||||
|
||||
================================================================================
|
||||
|
||||
const { x, y: /**/a, ...zs = {} } = {};
|
||||
|
||||
a
|
||||
y: a
|
||||
x, y: a, ...zs = {}
|
||||
{ x, y: a, ...zs = {} }
|
||||
const { x, y: a, ...zs = {} } = {};
|
||||
|
||||
================================================================================
|
||||
|
||||
const { x, y: a, .../**/zs = {} } = {};
|
||||
|
||||
zs
|
||||
...zs
|
||||
...zs = {}
|
||||
x, y: a, ...zs = {}
|
||||
{ x, y: a, ...zs = {} }
|
||||
const { x, y: a, ...zs = {} } = {};
|
||||
@@ -0,0 +1,12 @@
|
||||
type X<T, P> = IsExactlyAny<P> extends true ? T : ({ [K in keyof P]: IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[/**/K] : P[K]; } & Pick<T, Exclude<keyof T, keyof P>>)
|
||||
|
||||
K
|
||||
P[K]
|
||||
K extends keyof T ? T[K] : P[K]
|
||||
IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[K] : P[K]
|
||||
[K in keyof P]: IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[K] : P[K];
|
||||
{ [K in keyof P]: IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[K] : P[K]; }
|
||||
{ [K in keyof P]: IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[K] : P[K]; } & Pick<T, Exclude<keyof T, keyof P>>
|
||||
({ [K in keyof P]: IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[K] : P[K]; } & Pick<T, Exclude<keyof T, keyof P>>)
|
||||
IsExactlyAny<P> extends true ? T : ({ [K in keyof P]: IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[K] : P[K]; } & Pick<T, Exclude<keyof T, keyof P>>)
|
||||
type X<T, P> = IsExactlyAny<P> extends true ? T : ({ [K in keyof P]: IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[K] : P[K]; } & Pick<T, Exclude<keyof T, keyof P>>)
|
||||
@@ -0,0 +1,140 @@
|
||||
class HomePage {
|
||||
componentDidMount(/**/) {
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
↲
|
||||
••componentDidMount() {
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
}↲
|
||||
|
||||
|
||||
class HomePage {
|
||||
componentDidMount() {
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
================================================================================
|
||||
|
||||
class HomePage {
|
||||
componentDidMount() {
|
||||
if (this.props.username/**/) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
↲
|
||||
••••if (this.props.username) {
|
||||
return '';
|
||||
}↲
|
||||
••
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
↲
|
||||
••componentDidMount() {
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
}↲
|
||||
|
||||
|
||||
class HomePage {
|
||||
componentDidMount() {
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
================================================================================
|
||||
|
||||
class HomePage {
|
||||
componentDidMount() {
|
||||
if (this.props.username) {
|
||||
return '/**/';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
''
|
||||
|
||||
|
||||
return '';
|
||||
|
||||
|
||||
↲
|
||||
••••••return '';↲
|
||||
••••
|
||||
|
||||
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
↲
|
||||
••••if (this.props.username) {
|
||||
return '';
|
||||
}↲
|
||||
••
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
↲
|
||||
••componentDidMount() {
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
}↲
|
||||
|
||||
|
||||
class HomePage {
|
||||
componentDidMount() {
|
||||
if (this.props.username) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
function f(/**/p, q?, ...r: any[] = []) {}
|
||||
|
||||
p
|
||||
p, q?, ...r: any[] = []
|
||||
function f(p, q?, ...r: any[] = []) {}
|
||||
|
||||
================================================================================
|
||||
|
||||
function f(p, /**/q?, ...r: any[] = []) {}
|
||||
|
||||
q
|
||||
q?
|
||||
p, q?, ...r: any[] = []
|
||||
function f(p, q?, ...r: any[] = []) {}
|
||||
|
||||
================================================================================
|
||||
|
||||
function f(p, q?, /**/...r: any[] = []) {}
|
||||
|
||||
...
|
||||
...r
|
||||
...r: any[]
|
||||
...r: any[] = []
|
||||
p, q?, ...r: any[] = []
|
||||
function f(p, q?, ...r: any[] = []) {}
|
||||
@@ -0,0 +1,18 @@
|
||||
function f(
|
||||
a,
|
||||
/**/b
|
||||
) {}
|
||||
|
||||
|
||||
b
|
||||
|
||||
|
||||
↲
|
||||
••a,
|
||||
b↲
|
||||
|
||||
|
||||
function f(
|
||||
a,
|
||||
b
|
||||
) {}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { /**/x as y, z } from './z';
|
||||
import { b } from './';
|
||||
|
||||
console.log(1);
|
||||
|
||||
|
||||
x
|
||||
|
||||
|
||||
x as y
|
||||
|
||||
|
||||
x as y, z
|
||||
|
||||
|
||||
{ x as y, z }
|
||||
|
||||
|
||||
import { x as y, z } from './z';
|
||||
|
||||
|
||||
import { x as y, z } from './z';
|
||||
import { b } from './';
|
||||
|
||||
|
||||
import { x as y, z } from './z';
|
||||
import { b } from './';
|
||||
|
||||
console.log(1);
|
||||
@@ -0,0 +1,4 @@
|
||||
const /**/x = 3;
|
||||
|
||||
x
|
||||
const x = 3;
|
||||
@@ -0,0 +1,65 @@
|
||||
type M = { /**/-readonly [K in keyof any]-?: any };
|
||||
|
||||
-
|
||||
-readonly
|
||||
-readonly [K in keyof any]-?
|
||||
-readonly [K in keyof any]-?: any
|
||||
{ -readonly [K in keyof any]-?: any }
|
||||
type M = { -readonly [K in keyof any]-?: any };
|
||||
|
||||
================================================================================
|
||||
|
||||
type M = { -re/**/adonly [K in keyof any]-?: any };
|
||||
|
||||
readonly
|
||||
-readonly
|
||||
-readonly [K in keyof any]-?
|
||||
-readonly [K in keyof any]-?: any
|
||||
{ -readonly [K in keyof any]-?: any }
|
||||
type M = { -readonly [K in keyof any]-?: any };
|
||||
|
||||
================================================================================
|
||||
|
||||
type M = { -readonly /**/[K in keyof any]-?: any };
|
||||
|
||||
[
|
||||
[K in keyof any]
|
||||
-readonly [K in keyof any]-?
|
||||
-readonly [K in keyof any]-?: any
|
||||
{ -readonly [K in keyof any]-?: any }
|
||||
type M = { -readonly [K in keyof any]-?: any };
|
||||
|
||||
================================================================================
|
||||
|
||||
type M = { -readonly [K in ke/**/yof any]-?: any };
|
||||
|
||||
keyof
|
||||
keyof any
|
||||
K in keyof any
|
||||
[K in keyof any]
|
||||
-readonly [K in keyof any]-?
|
||||
-readonly [K in keyof any]-?: any
|
||||
{ -readonly [K in keyof any]-?: any }
|
||||
type M = { -readonly [K in keyof any]-?: any };
|
||||
|
||||
================================================================================
|
||||
|
||||
type M = { -readonly [K in keyof any]/**/-?: any };
|
||||
|
||||
-
|
||||
-?
|
||||
-readonly [K in keyof any]-?
|
||||
-readonly [K in keyof any]-?: any
|
||||
{ -readonly [K in keyof any]-?: any }
|
||||
type M = { -readonly [K in keyof any]-?: any };
|
||||
|
||||
================================================================================
|
||||
|
||||
type M = { -readonly [K in keyof any]-/**/?: any };
|
||||
|
||||
?
|
||||
-?
|
||||
-readonly [K in keyof any]-?
|
||||
-readonly [K in keyof any]-?: any
|
||||
{ -readonly [K in keyof any]-?: any }
|
||||
type M = { -readonly [K in keyof any]-?: any };
|
||||
@@ -0,0 +1,174 @@
|
||||
type X = {
|
||||
/**/foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
|
||||
foo
|
||||
|
||||
|
||||
foo?
|
||||
|
||||
|
||||
foo?: string;
|
||||
|
||||
|
||||
↲
|
||||
••foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh↲
|
||||
|
||||
|
||||
{
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
type X = {
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
================================================================================
|
||||
|
||||
type X = {
|
||||
foo?: string;
|
||||
/**/readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
|
||||
readonly
|
||||
|
||||
|
||||
readonly bar
|
||||
|
||||
|
||||
readonly bar: { x: number };
|
||||
|
||||
|
||||
↲
|
||||
••foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh↲
|
||||
|
||||
|
||||
{
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
type X = {
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
================================================================================
|
||||
|
||||
type X = {
|
||||
foo?: string;
|
||||
readonly /**/bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
|
||||
bar
|
||||
|
||||
|
||||
readonly bar
|
||||
|
||||
|
||||
readonly bar: { x: number };
|
||||
|
||||
|
||||
↲
|
||||
••foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh↲
|
||||
|
||||
|
||||
{
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
type X = {
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
================================================================================
|
||||
|
||||
type X = {
|
||||
foo?: string;
|
||||
readonly bar: { x: num/**/ber };
|
||||
meh
|
||||
}
|
||||
|
||||
|
||||
number
|
||||
|
||||
|
||||
x: number
|
||||
|
||||
|
||||
{ x: number }
|
||||
|
||||
|
||||
readonly bar: { x: number };
|
||||
|
||||
|
||||
↲
|
||||
••foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh↲
|
||||
|
||||
|
||||
{
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
type X = {
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
================================================================================
|
||||
|
||||
type X = {
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
/**/meh
|
||||
}
|
||||
|
||||
|
||||
meh
|
||||
|
||||
|
||||
↲
|
||||
••foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh↲
|
||||
|
||||
|
||||
{
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
|
||||
type X = {
|
||||
foo?: string;
|
||||
readonly bar: { x: number };
|
||||
meh
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
class Foo {
|
||||
bar(a, b) {
|
||||
if (/**/a === b) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
a
|
||||
|
||||
|
||||
a === b
|
||||
|
||||
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
↲
|
||||
••••••if (a === b) {
|
||||
return true;
|
||||
}
|
||||
return false;↲
|
||||
••
|
||||
|
||||
|
||||
bar(a, b) {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
↲
|
||||
••bar(a, b) {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}↲
|
||||
|
||||
|
||||
class Foo {
|
||||
bar(a, b) {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
================================================================================
|
||||
|
||||
class Foo {
|
||||
bar(a, b) {
|
||||
if (a === b) {
|
||||
return tr/**/ue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
true
|
||||
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
↲
|
||||
••••••••••return true;↲
|
||||
••••••
|
||||
|
||||
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
↲
|
||||
••••••if (a === b) {
|
||||
return true;
|
||||
}
|
||||
return false;↲
|
||||
••
|
||||
|
||||
|
||||
bar(a, b) {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
↲
|
||||
••bar(a, b) {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}↲
|
||||
|
||||
|
||||
class Foo {
|
||||
bar(a, b) {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
export interface IService {
|
||||
_serviceBrand: any;
|
||||
|
||||
open(ho/**/st: number, data: any): Promise<any>;
|
||||
bar(): void
|
||||
}
|
||||
|
||||
|
||||
host
|
||||
|
||||
|
||||
host: number
|
||||
|
||||
|
||||
host: number, data: any
|
||||
|
||||
|
||||
open(host: number, data: any): Promise<any>;
|
||||
|
||||
|
||||
↲
|
||||
••_serviceBrand: any;
|
||||
|
||||
open(host: number, data: any): Promise<any>;
|
||||
bar(): void↲
|
||||
|
||||
|
||||
export interface IService {
|
||||
_serviceBrand: any;
|
||||
|
||||
open(host: number, data: any): Promise<any>;
|
||||
bar(): void
|
||||
}
|
||||
|
||||
================================================================================
|
||||
|
||||
export interface IService {
|
||||
_serviceBrand: any;
|
||||
|
||||
open(host: number, data: any): Promise<any>;
|
||||
bar(): void/**/
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
|
||||
|
||||
bar(): void
|
||||
|
||||
|
||||
↲
|
||||
••_serviceBrand: any;
|
||||
|
||||
open(host: number, data: any): Promise<any>;
|
||||
bar(): void↲
|
||||
|
||||
|
||||
export interface IService {
|
||||
_serviceBrand: any;
|
||||
|
||||
open(host: number, data: any): Promise<any>;
|
||||
bar(): void
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
`a /**/b ${
|
||||
'c'
|
||||
} d`
|
||||
|
||||
|
||||
a b ${
|
||||
'c'
|
||||
} d
|
||||
|
||||
`a b ${
|
||||
'c'
|
||||
} d`
|
||||
|
||||
================================================================================
|
||||
|
||||
`a b ${
|
||||
'/**/c'
|
||||
} d`
|
||||
|
||||
|
||||
c
|
||||
|
||||
|
||||
'c'
|
||||
|
||||
|
||||
${
|
||||
'c'
|
||||
}
|
||||
|
||||
a b ${
|
||||
'c'
|
||||
} d
|
||||
|
||||
`a b ${
|
||||
'c'
|
||||
} d`
|
||||
@@ -0,0 +1,8 @@
|
||||
// @noEmit: true
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
// @Filename: loop.js
|
||||
var loop1 = loop2;
|
||||
var loop2 = loop1;
|
||||
|
||||
module.exports = loop2;
|
||||
@@ -244,6 +244,7 @@ declare namespace FourSlashInterface {
|
||||
baselineGetEmitOutput(insertResultsIntoVfs?: boolean): void;
|
||||
getEmitOutput(expectedOutputFiles: ReadonlyArray<string>): void;
|
||||
baselineQuickInfo(): void;
|
||||
baselineSmartSelection(): void;
|
||||
nameOrDottedNameSpanTextIs(text: string): void;
|
||||
outliningSpansInCurrentFile(spans: Range[]): void;
|
||||
todoCommentsInCurrentFile(descriptors: string[]): void;
|
||||
@@ -508,7 +509,7 @@ declare namespace FourSlashInterface {
|
||||
readonly importModuleSpecifierEnding?: "minimal" | "index" | "js";
|
||||
}
|
||||
interface CompletionsOptions {
|
||||
readonly marker?: ArrayOrSingle<string | Marker>,
|
||||
readonly marker?: ArrayOrSingle<string | Marker>;
|
||||
readonly isNewIdentifierLocation?: boolean;
|
||||
readonly isGlobalCompletion?: boolean;
|
||||
readonly exact?: ArrayOrSingle<ExpectedCompletionEntry>;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////// Not a JSDoc comment
|
||||
/////**
|
||||
//// * @param {number} x The number to square
|
||||
//// */
|
||||
////function /**/square(x) {
|
||||
//// return x * x;
|
||||
////}
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,6 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////let/**/ x: string
|
||||
|
||||
// Verifies that the selection goes to 'let' first even though it’s behind the caret
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,5 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////const { /*1*/x, y: /*2*/a, .../*3*/zs = {} } = {};
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,5 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////type X<T, P> = IsExactlyAny<P> extends true ? T : ({ [K in keyof P]: IsExactlyAny<P[K]> extends true ? K extends keyof T ? T[K] : P[/**/K] : P[K]; } & Pick<T, Exclude<keyof T, keyof P>>)
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,11 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////class HomePage {
|
||||
//// componentDidMount(/*1*/) {
|
||||
//// if (this.props.username/*2*/) {
|
||||
//// return '/*3*/';
|
||||
//// }
|
||||
//// }
|
||||
////}
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,5 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////function f(/*1*/p, /*2*/q?, /*3*/...r: any[] = []) {}
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,8 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////function f(
|
||||
//// a,
|
||||
//// /**/b
|
||||
////) {}
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,8 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////import { /**/x as y, z } from './z';
|
||||
////import { b } from './';
|
||||
////
|
||||
////console.log(1);
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,5 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////const /**/x = 3;
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,5 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////type M = { /*1*/-re/*2*/adonly /*3*/[K in ke/*4*/yof any]/*5*/-/*6*/?: any };
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,9 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////type X = {
|
||||
//// /*1*/foo?: string;
|
||||
//// /*2*/readonly /*3*/bar: { x: num/*4*/ber };
|
||||
//// /*5*/meh
|
||||
////}
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,12 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////class Foo {
|
||||
//// bar(a, b) {
|
||||
//// if (/*1*/a === b) {
|
||||
//// return tr/*2*/ue;
|
||||
//// }
|
||||
//// return false;
|
||||
//// }
|
||||
////}
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,10 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////export interface IService {
|
||||
//// _serviceBrand: any;
|
||||
////
|
||||
//// open(ho/*1*/st: number, data: any): Promise<any>;
|
||||
//// bar(): void/*2*/
|
||||
////}
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
@@ -0,0 +1,7 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////`a /*1*/b ${
|
||||
//// '/*2*/c'
|
||||
////} d`
|
||||
|
||||
verify.baselineSmartSelection();
|
||||
Reference in New Issue
Block a user