Merge pull request #37029 from armanio123/AddToggleCommentFeature

Add ToggleLineComment and ToggleMultilineComment service
This commit is contained in:
Armando Aguirre 2020-07-13 17:48:50 -07:00 committed by GitHub
commit 6279f981b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1262 additions and 33 deletions

View file

@ -825,6 +825,22 @@ namespace ts.server {
return notImplemented();
}
toggleLineComment(): TextChange[] {
return notImplemented();
}
toggleMultilineComment(): TextChange[] {
return notImplemented();
}
commentSelection(): TextChange[] {
return notImplemented();
}
uncommentSelection(): TextChange[] {
return notImplemented();
}
dispose(): void {
throw new Error("dispose is not available through the server layer.");
}

View file

@ -3226,7 +3226,7 @@ namespace FourSlash {
this.raiseError(
`Expected to find a fix with the name '${fixName}', but none exists.` +
availableFixes.length
availableFixes.length
? ` Available fixes: ${availableFixes.map(fix => `${fix.fixName} (${fix.fixId ? "with" : "without"} fix-all)`).join(", ")}`
: ""
);
@ -3465,13 +3465,13 @@ namespace FourSlash {
const incomingCalls =
direction === CallHierarchyItemDirection.Outgoing ? { result: "skip" } as const :
alreadySeen ? { result: "seen" } as const :
{ result: "show", values: this.languageService.provideCallHierarchyIncomingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start) } as const;
alreadySeen ? { result: "seen" } as const :
{ result: "show", values: this.languageService.provideCallHierarchyIncomingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start) } as const;
const outgoingCalls =
direction === CallHierarchyItemDirection.Incoming ? { result: "skip" } as const :
alreadySeen ? { result: "seen" } as const :
{ result: "show", values: this.languageService.provideCallHierarchyOutgoingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start) } as const;
alreadySeen ? { result: "seen" } as const :
{ result: "show", values: this.languageService.provideCallHierarchyOutgoingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start) } as const;
let text = "";
text += `${prefix}╭ name: ${callHierarchyItem.name}\n`;
@ -3485,7 +3485,7 @@ namespace FourSlash {
text += `${prefix}├ selectionSpan:\n`;
text += this.formatCallHierarchyItemSpan(file, callHierarchyItem.selectionSpan, `${prefix}`,
incomingCalls.result !== "skip" || outgoingCalls.result !== "skip" ? `${prefix}` :
`${trailingPrefix}`);
`${trailingPrefix}`);
if (incomingCalls.result === "seen") {
if (outgoingCalls.result === "skip") {
@ -3514,8 +3514,8 @@ namespace FourSlash {
text += `${prefix}│ ├ fromSpans:\n`;
text += this.formatCallHierarchyItemSpans(file, incomingCall.fromSpans, `${prefix}│ │ `,
i < incomingCalls.values.length - 1 ? `${prefix}│ ╰ ` :
outgoingCalls.result !== "skip" ? `${prefix}│ ╰ ` :
`${trailingPrefix}╰ ╰ `);
outgoingCalls.result !== "skip" ? `${prefix}│ ╰ ` :
`${trailingPrefix}╰ ╰ `);
}
}
}
@ -3536,7 +3536,7 @@ namespace FourSlash {
text += `${prefix}│ ├ fromSpans:\n`;
text += this.formatCallHierarchyItemSpans(file, outgoingCall.fromSpans, `${prefix}│ │ `,
i < outgoingCalls.values.length - 1 ? `${prefix}│ ╰ ` :
`${trailingPrefix}╰ ╰ `);
`${trailingPrefix}╰ ╰ `);
}
}
}
@ -3696,6 +3696,50 @@ namespace FourSlash {
public configurePlugin(pluginName: string, configuration: any): void {
(<ts.server.SessionClient>this.languageService).configurePlugin(pluginName, configuration);
}
public toggleLineComment(newFileContent: string): void {
const changes: ts.TextChange[] = [];
for (const range of this.getRanges()) {
changes.push.apply(changes, this.languageService.toggleLineComment(this.activeFile.fileName, range));
}
this.applyEdits(this.activeFile.fileName, changes);
this.verifyCurrentFileContent(newFileContent);
}
public toggleMultilineComment(newFileContent: string): void {
const changes: ts.TextChange[] = [];
for (const range of this.getRanges()) {
changes.push.apply(changes, this.languageService.toggleMultilineComment(this.activeFile.fileName, range));
}
this.applyEdits(this.activeFile.fileName, changes);
this.verifyCurrentFileContent(newFileContent);
}
public commentSelection(newFileContent: string): void {
const changes: ts.TextChange[] = [];
for (const range of this.getRanges()) {
changes.push.apply(changes, this.languageService.commentSelection(this.activeFile.fileName, range));
}
this.applyEdits(this.activeFile.fileName, changes);
this.verifyCurrentFileContent(newFileContent);
}
public uncommentSelection(newFileContent: string): void {
const changes: ts.TextChange[] = [];
for (const range of this.getRanges()) {
changes.push.apply(changes, this.languageService.uncommentSelection(this.activeFile.fileName, range));
}
this.applyEdits(this.activeFile.fileName, changes);
this.verifyCurrentFileContent(newFileContent);
}
}
function prefixMessage(message: string | undefined) {

View file

@ -214,6 +214,22 @@ namespace FourSlashInterface {
public refactorAvailableForTriggerReason(triggerReason: ts.RefactorTriggerReason, name: string, actionName?: string) {
this.state.verifyRefactorAvailable(this.negative, triggerReason, name, actionName);
}
public toggleLineComment(newFileContent: string) {
this.state.toggleLineComment(newFileContent);
}
public toggleMultilineComment(newFileContent: string) {
this.state.toggleMultilineComment(newFileContent);
}
public commentSelection(newFileContent: string) {
this.state.commentSelection(newFileContent);
}
public uncommentSelection(newFileContent: string) {
this.state.uncommentSelection(newFileContent);
}
}
export class Verify extends VerifyNegatable {

View file

@ -603,6 +603,18 @@ namespace Harness.LanguageService {
clearSourceMapperCache(): never {
return ts.notImplemented();
}
toggleLineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] {
return unwrapJSONCallResult(this.shim.toggleLineComment(fileName, textRange));
}
toggleMultilineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] {
return unwrapJSONCallResult(this.shim.toggleMultilineComment(fileName, textRange));
}
commentSelection(fileName: string, textRange: ts.TextRange): ts.TextChange[] {
return unwrapJSONCallResult(this.shim.commentSelection(fileName, textRange));
}
uncommentSelection(fileName: string, textRange: ts.TextRange): ts.TextChange[] {
return unwrapJSONCallResult(this.shim.uncommentSelection(fileName, textRange));
}
dispose(): void { this.shim.dispose({}); }
}

View file

@ -136,7 +136,18 @@ namespace ts.server.protocol {
SelectionRange = "selectionRange",
/* @internal */
SelectionRangeFull = "selectionRange-full",
ToggleLineComment = "toggleLineComment",
/* @internal */
ToggleLineCommentFull = "toggleLineComment-full",
ToggleMultilineComment = "toggleMultilineComment",
/* @internal */
ToggleMultilineCommentFull = "toggleMultilineComment-full",
CommentSelection = "commentSelection",
/* @internal */
CommentSelectionFull = "commentSelection-full",
UncommentSelection = "uncommentSelection",
/* @internal */
UncommentSelectionFull = "uncommentSelection-full",
PrepareCallHierarchy = "prepareCallHierarchy",
ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls",
ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls",
@ -1542,6 +1553,26 @@ namespace ts.server.protocol {
parent?: SelectionRange;
}
export interface ToggleLineCommentRequest extends FileRequest {
command: CommandTypes.ToggleLineComment;
arguments: FileRangeRequestArgs;
}
export interface ToggleMultilineCommentRequest extends FileRequest {
command: CommandTypes.ToggleMultilineComment;
arguments: FileRangeRequestArgs;
}
export interface CommentSelectionRequest extends FileRequest {
command: CommandTypes.CommentSelection;
arguments: FileRangeRequestArgs;
}
export interface UncommentSelectionRequest extends FileRequest {
command: CommandTypes.UncommentSelection;
arguments: FileRangeRequestArgs;
}
/**
* Information found in an "open" request.
*/

View file

@ -2012,8 +2012,7 @@ namespace ts.server {
position = getPosition(args);
}
else {
const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo);
textRange = { pos: startPosition, end: endPosition };
textRange = this.getRange(args, scriptInfo);
}
return Debug.checkDefined(position === undefined ? textRange : position);
@ -2022,6 +2021,12 @@ namespace ts.server {
}
}
private getRange(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo): TextRange {
const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo);
return { pos: startPosition, end: endPosition };
}
private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file)!;
@ -2251,6 +2256,70 @@ namespace ts.server {
});
}
private toggleLineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] {
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const scriptInfo = this.projectService.getScriptInfo(file)!;
const textRange = this.getRange(args, scriptInfo);
const textChanges = languageService.toggleLineComment(file, textRange);
if (simplifiedResult) {
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo));
}
return textChanges;
}
private toggleMultilineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] {
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
const textRange = this.getRange(args, scriptInfo);
const textChanges = languageService.toggleMultilineComment(file, textRange);
if (simplifiedResult) {
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo));
}
return textChanges;
}
private commentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] {
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
const textRange = this.getRange(args, scriptInfo);
const textChanges = languageService.commentSelection(file, textRange);
if (simplifiedResult) {
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo));
}
return textChanges;
}
private uncommentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] {
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
const textRange = this.getRange(args, scriptInfo);
const textChanges = languageService.uncommentSelection(file, textRange);
if (simplifiedResult) {
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo));
}
return textChanges;
}
private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange {
const result: protocol.SelectionRange = {
textSpan: toProtocolTextSpan(selectionRange.textSpan, scriptInfo),
@ -2697,6 +2766,30 @@ namespace ts.server {
[CommandNames.ProvideCallHierarchyOutgoingCalls]: (request: protocol.ProvideCallHierarchyOutgoingCallsRequest) => {
return this.requiredResponse(this.provideCallHierarchyOutgoingCalls(request.arguments));
},
[CommandNames.ToggleLineComment]: (request: protocol.ToggleLineCommentRequest) => {
return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.ToggleLineCommentFull]: (request: protocol.ToggleLineCommentRequest) => {
return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.ToggleMultilineComment]: (request: protocol.ToggleMultilineCommentRequest) => {
return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.ToggleMultilineCommentFull]: (request: protocol.ToggleMultilineCommentRequest) => {
return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.CommentSelection]: (request: protocol.CommentSelectionRequest) => {
return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.CommentSelectionFull]: (request: protocol.CommentSelectionRequest) => {
return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ false));
},
[CommandNames.UncommentSelection]: (request: protocol.UncommentSelectionRequest) => {
return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.UncommentSelectionFull]: (request: protocol.UncommentSelectionRequest) => {
return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ false));
},
}));
public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {

View file

@ -5,8 +5,8 @@ namespace ts {
function createNode<TKind extends SyntaxKind>(kind: TKind, pos: number, end: number, parent: Node): NodeObject | TokenObject<TKind> | IdentifierObject | PrivateIdentifierObject {
const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) :
kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) :
kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) :
new TokenObject(kind, pos, end);
kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) :
new TokenObject(kind, pos, end);
node.parent = parent;
node.flags = parent.flags & NodeFlags.ContextFlags;
return node;
@ -773,7 +773,7 @@ namespace ts {
if (!hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) {
break;
}
// falls through
// falls through
case SyntaxKind.VariableDeclaration:
case SyntaxKind.BindingElement: {
@ -834,7 +834,7 @@ namespace ts {
if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) {
addDeclaration(node as BinaryExpression);
}
// falls through
// falls through
default:
forEachChild(node, visit);
@ -1994,6 +1994,236 @@ namespace ts {
}
}
function getLinesForRange(sourceFile: SourceFile, textRange: TextRange) {
return {
lineStarts: sourceFile.getLineStarts(),
firstLine: sourceFile.getLineAndCharacterOfPosition(textRange.pos).line,
lastLine: sourceFile.getLineAndCharacterOfPosition(textRange.end).line
};
}
function toggleLineComment(fileName: string, textRange: TextRange, insertComment?: boolean): TextChange[] {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const textChanges: TextChange[] = [];
const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange);
let isCommenting = insertComment || false;
let leftMostPosition = Number.MAX_VALUE;
const lineTextStarts = new Map<string, number>();
const firstNonWhitespaceCharacterRegex = new RegExp(/\S/);
const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]);
const openComment = isJsx ? "{/*" : "//";
// Check each line before any text changes.
for (let i = firstLine; i <= lastLine; i++) {
const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i]));
// Find the start of text and the left-most character. No-op on empty lines.
const regExec = firstNonWhitespaceCharacterRegex.exec(lineText);
if (regExec) {
leftMostPosition = Math.min(leftMostPosition, regExec.index);
lineTextStarts.set(i.toString(), regExec.index);
if (lineText.substr(regExec.index, openComment.length) !== openComment) {
isCommenting = insertComment === undefined || insertComment;
}
}
}
// Push all text changes.
for (let i = firstLine; i <= lastLine; i++) {
const lineTextStart = lineTextStarts.get(i.toString());
// If the line is not an empty line; otherwise no-op.
if (lineTextStart !== undefined) {
if (isJsx) {
textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }, isCommenting, isJsx));
}
else if (isCommenting) {
textChanges.push({
newText: openComment,
span: {
length: 0,
start: lineStarts[i] + leftMostPosition
}
});
}
else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) {
textChanges.push({
newText: "",
span: {
length: openComment.length,
start: lineStarts[i] + lineTextStart
}
});
}
}
}
return textChanges;
}
function toggleMultilineComment(fileName: string, textRange: TextRange, insertComment?: boolean, isInsideJsx?: boolean): TextChange[] {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const textChanges: TextChange[] = [];
const { text } = sourceFile;
let hasComment = false;
let isCommenting = insertComment || false;
const positions = [] as number[] as SortedArray<number>;
let { pos } = textRange;
const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos);
const openMultiline = isJsx ? "{/*" : "/*";
const closeMultiline = isJsx ? "*/}" : "*/";
const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*";
const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/";
// Get all comment positions
while (pos <= textRange.end) {
// Start of comment is considered inside comment.
const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0;
const commentRange = isInComment(sourceFile, pos + offset);
// If position is in a comment add it to the positions array.
if (commentRange) {
// Comment range doesn't include the brace character. Increase it to include them.
if (isJsx) {
commentRange.pos--;
commentRange.end++;
}
positions.push(commentRange.pos);
if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) {
positions.push(commentRange.end);
}
hasComment = true;
pos = commentRange.end + 1;
}
else { // If it's not in a comment range, then we need to comment the uncommented portions.
const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`);
isCommenting = insertComment !== undefined
? insertComment
: isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); // If isCommenting is already true we don't need to check whitespace again.
pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length;
}
}
// If it didn't found a comment and isCommenting is false means is only empty space.
// We want to insert comment in this scenario.
if (isCommenting || !hasComment) {
if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) {
insertSorted(positions, textRange.pos, compareValues);
}
insertSorted(positions, textRange.end, compareValues);
// Insert open comment if the first position is not a comment already.
const firstPos = positions[0];
if (text.substr(firstPos, openMultiline.length) !== openMultiline) {
textChanges.push({
newText: openMultiline,
span: {
length: 0,
start: firstPos
}
});
}
// Insert open and close comment to all positions between first and last. Exclusive.
for (let i = 1; i < positions.length - 1; i++) {
if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) {
textChanges.push({
newText: closeMultiline,
span: {
length: 0,
start: positions[i]
}
});
}
if (text.substr(positions[i], openMultiline.length) !== openMultiline) {
textChanges.push({
newText: openMultiline,
span: {
length: 0,
start: positions[i]
}
});
}
}
// Insert open comment if the last position is not a comment already.
if (textChanges.length % 2 !== 0) {
textChanges.push({
newText: closeMultiline,
span: {
length: 0,
start: positions[positions.length - 1]
}
});
}
}
else {
// If is not commenting then remove all comments found.
for (const pos of positions) {
const from = pos - closeMultiline.length > 0 ? pos - closeMultiline.length : 0;
const offset = text.substr(from, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0;
textChanges.push({
newText: "",
span: {
length: openMultiline.length,
start: pos - offset
}
});
}
}
return textChanges;
}
function commentSelection(fileName: string, textRange: TextRange): TextChange[] {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const { firstLine, lastLine } = getLinesForRange(sourceFile, textRange);
// If there is a selection that is on the same line, add multiline.
return firstLine === lastLine && textRange.pos !== textRange.end
? toggleMultilineComment(fileName, textRange, /*insertComment*/ true)
: toggleLineComment(fileName, textRange, /*insertComment*/ true);
}
function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const textChanges: TextChange[] = [];
const { pos } = textRange;
let { end } = textRange;
// If cursor is not a selection we need to increase the end position
// to include the start of the comment.
if (pos === end) {
end += isInsideJsxElement(sourceFile, pos) ? 2 : 1;
}
for (let i = pos; i <= end; i++) {
const commentRange = isInComment(sourceFile, i);
if (commentRange) {
switch (commentRange.kind) {
case SyntaxKind.SingleLineCommentTrivia:
textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false));
break;
case SyntaxKind.MultiLineCommentTrivia:
textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false));
}
i = commentRange.end + 1;
}
}
return textChanges;
}
function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean {
return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) ||
isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent);
@ -2274,7 +2504,11 @@ namespace ts {
clearSourceMapperCache: () => sourceMapper.clearCache(),
prepareCallHierarchy,
provideCallHierarchyIncomingCalls,
provideCallHierarchyOutgoingCalls
provideCallHierarchyOutgoingCalls,
toggleLineComment,
toggleMultilineComment,
commentSelection,
uncommentSelection,
};
if (syntaxOnly) {
@ -2348,7 +2582,7 @@ namespace ts {
if (node.parent.kind === SyntaxKind.ComputedPropertyName) {
return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined;
}
// falls through
// falls through
case SyntaxKind.Identifier:
return isObjectLiteralElement(node.parent) &&

View file

@ -277,6 +277,11 @@ namespace ts {
getEmitOutput(fileName: string): string;
getEmitOutputObject(fileName: string): EmitOutput;
toggleLineComment(fileName: string, textChange: TextRange): string;
toggleMultilineComment(fileName: string, textChange: TextRange): string;
commentSelection(fileName: string, textChange: TextRange): string;
uncommentSelection(fileName: string, textChange: TextRange): string;
}
export interface ClassifierShim extends Shim {
@ -1068,6 +1073,34 @@ namespace ts {
() => this.languageService.getEmitOutput(fileName),
this.logPerformance) as EmitOutput;
}
public toggleLineComment(fileName: string, textRange: TextRange): string {
return this.forwardJSONCall(
`toggleLineComment('${fileName}', '${JSON.stringify(textRange)}')`,
() => this.languageService.toggleLineComment(fileName, textRange)
);
}
public toggleMultilineComment(fileName: string, textRange: TextRange): string {
return this.forwardJSONCall(
`toggleMultilineComment('${fileName}', '${JSON.stringify(textRange)}')`,
() => this.languageService.toggleMultilineComment(fileName, textRange)
);
}
public commentSelection(fileName: string, textRange: TextRange): string {
return this.forwardJSONCall(
`commentSelection('${fileName}', '${JSON.stringify(textRange)}')`,
() => this.languageService.commentSelection(fileName, textRange)
);
}
public uncommentSelection(fileName: string, textRange: TextRange): string {
return this.forwardJSONCall(
`uncommentSelection('${fileName}', '${JSON.stringify(textRange)}')`,
() => this.languageService.uncommentSelection(fileName, textRange)
);
}
}
function convertClassifications(classifications: Classifications): { spans: string, endOfLineState: EndOfLineState } {

View file

@ -506,6 +506,11 @@ namespace ts {
/* @internal */ getNonBoundSourceFile(fileName: string): SourceFile;
/* @internal */ getAutoImportProvider(): Program | undefined;
toggleLineComment(fileName: string, textRange: TextRange): TextChange[];
toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[];
commentSelection(fileName: string, textRange: TextRange): TextChange[];
uncommentSelection(fileName: string, textRange: TextRange): TextChange[];
dispose(): void;
}

View file

@ -394,7 +394,7 @@ namespace ts {
case SyntaxKind.MethodSignature:
return ScriptElementKind.memberFunctionElement;
case SyntaxKind.PropertyAssignment:
const {initializer} = node as PropertyAssignment;
const { initializer } = node as PropertyAssignment;
return isFunctionLike(initializer) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
@ -557,7 +557,7 @@ namespace ts {
if (!(<NewExpression>n).arguments) {
return true;
}
// falls through
// falls through
case SyntaxKind.CallExpression:
case SyntaxKind.ParenthesizedExpression:
@ -877,14 +877,14 @@ namespace ts {
// specially by `getSymbolAtLocation`.
if (isModifier(node) && (forRename || node.kind !== SyntaxKind.DefaultKeyword) ? contains(parent.modifiers, node) :
node.kind === SyntaxKind.ClassKeyword ? isClassDeclaration(parent) || isClassExpression(node) :
node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) || isFunctionExpression(node) :
node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) :
node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) :
node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) :
node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) :
node.kind === SyntaxKind.ImportKeyword ? isImportEqualsDeclaration(parent) :
node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) :
node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) {
node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) || isFunctionExpression(node) :
node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) :
node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) :
node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) :
node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) :
node.kind === SyntaxKind.ImportKeyword ? isImportEqualsDeclaration(parent) :
node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) :
node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) {
const location = getAdjustedLocationForDeclaration(parent, forRename);
if (location) {
return location;
@ -1320,6 +1320,35 @@ namespace ts {
return false;
}
export function isInsideJsxElement(sourceFile: SourceFile, position: number): boolean {
function isInsideJsxElementTraversal(node: Node): boolean {
while (node) {
if (node.kind >= SyntaxKind.JsxSelfClosingElement && node.kind <= SyntaxKind.JsxExpression
|| node.kind === SyntaxKind.JsxText
|| node.kind === SyntaxKind.LessThanToken
|| node.kind === SyntaxKind.GreaterThanToken
|| node.kind === SyntaxKind.Identifier
|| node.kind === SyntaxKind.CloseBraceToken
|| node.kind === SyntaxKind.OpenBraceToken
|| node.kind === SyntaxKind.SlashToken) {
node = node.parent;
}
else if (node.kind === SyntaxKind.JsxElement) {
if (position > node.getStart(sourceFile)) return true;
node = node.parent;
}
else {
return false;
}
}
return false;
}
return isInsideJsxElementTraversal(getTokenAtPosition(sourceFile, position));
}
export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind, sourceFile: SourceFile) {
const tokenKind = token.kind;
let remainingMatchingTokens = 0;
@ -1346,7 +1375,7 @@ namespace ts {
export function removeOptionality(type: Type, isOptionalExpression: boolean, isOptionalChain: boolean) {
return isOptionalExpression ? type.getNonNullableType() :
isOptionalChain ? type.getNonOptionalType() :
type;
type;
}
export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile, checker: TypeChecker): boolean {
@ -1439,7 +1468,7 @@ namespace ts {
break;
case SyntaxKind.EqualsGreaterThanToken:
// falls through
// falls through
case SyntaxKind.Identifier:
case SyntaxKind.StringLiteral:
@ -1447,7 +1476,7 @@ namespace ts {
case SyntaxKind.BigIntLiteral:
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
// falls through
// falls through
case SyntaxKind.TypeOfKeyword:
case SyntaxKind.ExtendsKeyword:
@ -1934,6 +1963,16 @@ namespace ts {
return undefined;
}
export function isTextWhiteSpaceLike(text: string, startPos: number, endPos: number): boolean {
for (let i = startPos; i < endPos; i++) {
if (!isWhiteSpaceLike(text.charCodeAt(i))) {
return false;
}
}
return true;
}
// #endregion
// Display-part writer helpers
@ -2242,8 +2281,8 @@ namespace ts {
// This only happens for leaf nodes - internal nodes always see their children change.
const clone =
isStringLiteral(node) ? setOriginalNode(factory.createStringLiteralFromNode(node), node) as Node as T :
isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T :
factory.cloneNode(node);
isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T :
factory.cloneNode(node);
return setTextRange(clone, node);
}

View file

@ -272,6 +272,10 @@ namespace ts.server {
CommandNames.PrepareCallHierarchy,
CommandNames.ProvideCallHierarchyIncomingCalls,
CommandNames.ProvideCallHierarchyOutgoingCalls,
CommandNames.ToggleLineComment,
CommandNames.ToggleMultilineComment,
CommandNames.CommentSelection,
CommandNames.UncommentSelection,
];
it("should not throw when commands are executed with invalid arguments", () => {

View file

@ -5487,6 +5487,10 @@ declare namespace ts {
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[];
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput;
getProgram(): Program | undefined;
toggleLineComment(fileName: string, textRange: TextRange): TextChange[];
toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[];
commentSelection(fileName: string, textRange: TextRange): TextChange[];
uncommentSelection(fileName: string, textRange: TextRange): TextChange[];
dispose(): void;
}
interface JsxClosingTagInfo {
@ -6482,6 +6486,10 @@ declare namespace ts.server.protocol {
GetEditsForFileRename = "getEditsForFileRename",
ConfigurePlugin = "configurePlugin",
SelectionRange = "selectionRange",
ToggleLineComment = "toggleLineComment",
ToggleMultilineComment = "toggleMultilineComment",
CommentSelection = "commentSelection",
UncommentSelection = "uncommentSelection",
PrepareCallHierarchy = "prepareCallHierarchy",
ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls",
ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls"
@ -7514,6 +7522,22 @@ declare namespace ts.server.protocol {
textSpan: TextSpan;
parent?: SelectionRange;
}
interface ToggleLineCommentRequest extends FileRequest {
command: CommandTypes.ToggleLineComment;
arguments: FileRangeRequestArgs;
}
interface ToggleMultilineCommentRequest extends FileRequest {
command: CommandTypes.ToggleMultilineComment;
arguments: FileRangeRequestArgs;
}
interface CommentSelectionRequest extends FileRequest {
command: CommandTypes.CommentSelection;
arguments: FileRangeRequestArgs;
}
interface UncommentSelectionRequest extends FileRequest {
command: CommandTypes.UncommentSelection;
arguments: FileRangeRequestArgs;
}
/**
* Information found in an "open" request.
*/
@ -9870,6 +9894,7 @@ declare namespace ts.server {
private getSupportedCodeFixes;
private isLocation;
private extractPositionOrRange;
private getRange;
private getApplicableRefactors;
private getEditsForRefactor;
private organizeImports;
@ -9887,6 +9912,10 @@ declare namespace ts.server {
private getDiagnosticsForProject;
private configurePlugin;
private getSmartSelectionRange;
private toggleLineComment;
private toggleMultilineComment;
private commentSelection;
private uncommentSelection;
private mapSelectionRange;
private getScriptInfoFromProjectService;
private toProtocolCallHierarchyItem;

View file

@ -5487,6 +5487,10 @@ declare namespace ts {
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[];
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput;
getProgram(): Program | undefined;
toggleLineComment(fileName: string, textRange: TextRange): TextChange[];
toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[];
commentSelection(fileName: string, textRange: TextRange): TextChange[];
uncommentSelection(fileName: string, textRange: TextRange): TextChange[];
dispose(): void;
}
interface JsxClosingTagInfo {

View file

@ -0,0 +1,26 @@
// Simple comment selection cases.
//// let var1[| = 1;
//// let var2 = 2;
//// let var3 |]= 3;
////
//// let var4[| = 4;|]
////
//// let [||]var5 = 5;
////
//// //let var6[| = 6;
//// //let var7 = 7;
//// //let var8 |]= 8;
verify.commentSelection(
`//let var1 = 1;
//let var2 = 2;
//let var3 = 3;
let var4/* = 4;*/
//let var5 = 5;
////let var6 = 6;
////let var7 = 7;
////let var8 = 8;`);

View file

@ -0,0 +1,29 @@
// Common jsx insert comment.
//@Filename: file.tsx
//// const a = <MyContainer>
//// [|<MyFirstComponent />
//// <MySecondComponent />|]
//// </MyContainer>;
//// const b = <MyContainer>
//// {/*<MyF[|irstComponent />*/}
//// {/*<MySec|]ondComponent />*/}
//// </MyContainer>;
//// const c = <MyContainer>[|
//// <MyFirstComponent />
//// <MySecondCompo|]nent />
//// </MyContainer>;
verify.commentSelection(
`const a = <MyContainer>
{/*<MyFirstComponent />*/}
{/*<MySecondComponent />*/}
</MyContainer>;
const b = <MyContainer>
{/*<MyFirstComponent />*/}
{/*<MySecondComponent />*/}
</MyContainer>;
//const c = <MyContainer>
// <MyFirstComponent />
// <MySecondComponent />
</MyContainer>;`);

View file

@ -398,6 +398,11 @@ declare namespace FourSlashInterface {
generateTypes(...options: GenerateTypesOptions[]): void;
organizeImports(newContent: string): void;
toggleLineComment(newFileContent: string): void;
toggleMultilineComment(newFileContent: string): void;
commentSelection(newFileContent: string): void;
uncommentSelection(newFileContent: string): void;
}
class edit {
backspace(count?: number): void;

View file

@ -0,0 +1,18 @@
// Simple comment and uncomment.
//// let var1[| = 1;
//// let var2 = 2;
//// let var3 |]= 3;
////
//// //let var4[| = 1;
//// //let var5 = 2;
//// //let var6 |]= 3;
verify.toggleLineComment(
`//let var1 = 1;
//let var2 = 2;
//let var3 = 3;
let var4 = 1;
let var5 = 2;
let var6 = 3;`);

View file

@ -0,0 +1,11 @@
// Close and open multiline comments if the line already contains more comments.
//@Filename: file.tsx
//// const a = <div>
//// Som[||]e{/* T */}ext
//// </div>;
verify.toggleLineComment(
`const a = <div>
{/*Some*/}{/* T */}{/*ext*/}
</div>;`);

View file

@ -0,0 +1,20 @@
// When indentation is different between lines it should get the left most indentation
// and use that for all lines.
// When uncommeting, doesn't matter what indentation the line has.
//// let var1[| = 1;
//// let var2 = 2;
//// let var3 |]= 3;
////
//// // let var4[| = 1;
//// //let var5 = 2;
//// // let var6 |]= 3;
verify.toggleLineComment(
` // let var1 = 1;
//let var2 = 2;
// let var3 = 3;
let var4 = 1;
let var5 = 2;
let var6 = 3;`);

View file

@ -0,0 +1,26 @@
// Comment and uncomment ignores empty lines.
//// let var1[| = 1;
////
//// let var2 = 2;
////
//// let var3 |]= 3;
////
//// //let var4[| = 1;
////
//// //let var5 = 2;
////
//// //let var6 |]= 3;
verify.toggleLineComment(
`//let var1 = 1;
//let var2 = 2;
//let var3 = 3;
let var4 = 1;
let var5 = 2;
let var6 = 3;`);

View file

@ -0,0 +1,18 @@
// If at least one line is not commented then comment all lines again.
//// //const a[| = 1;
//// const b = 2
//// //const c =|] 3;
////
//// ////const d[| = 4;
//// //const e = 5;
//// ////const e =|] 6;
verify.toggleLineComment(
`// //const a = 1;
//const b = 2
// //const c = 3;
//const d = 4;
const e = 5;
//const e = 6;`);

View file

@ -0,0 +1,22 @@
// Comments inside strings are still considered comments.
//// let var1 = `
//// //some stri[|ng
//// //some other|] string
//// `;
////
//// let var2 = `
//// some stri[|ng
//// some other|] string
//// `;
verify.toggleLineComment(
`let var1 = \`
some string
some other string
\`;
let var2 = \`
//some string
//some other string
\`;`);

View file

@ -0,0 +1,15 @@
// Selection is at the start of jsx its still js.
//@Filename: file.tsx
//// let a = (
//// [|<div>
//// some text|]
//// </div>
//// );
verify.toggleLineComment(
`let a = (
//<div>
// some text
</div>
);`);

View file

@ -0,0 +1,29 @@
// Common comment line cases.
//@Filename: file.tsx
//// const a = <MyContainer>
//// [|<MyFirstComponent />
//// <MySecondComponent />|]
//// </MyContainer>;
//// const b = <MyContainer>
//// {/*<MyF[|irstComponent />*/}
//// {/*<MySec|]ondComponent />*/}
//// </MyContainer>;
//// const c = <MyContainer>[|
//// <MyFirstComponent />
//// <MySecondCompo|]nent />
//// </MyContainer>;
verify.toggleLineComment(
`const a = <MyContainer>
{/*<MyFirstComponent />*/}
{/*<MySecondComponent />*/}
</MyContainer>;
const b = <MyContainer>
<MyFirstComponent />
<MySecondComponent />
</MyContainer>;
//const c = <MyContainer>
// <MyFirstComponent />
// <MySecondComponent />
</MyContainer>;`);

View file

@ -0,0 +1,30 @@
// When indentation is different between lines it should get the left most indentation
// and use that for all lines.
// When uncommeting, doesn't matter what indentation the line has.
//@Filename: file.tsx
//// const a = <div>
//// [|<div>
//// SomeText
//// </div>|]
//// </div>;
////
//// const b = <div>
//// {/*[|<div>*/}
//// {/* SomeText*/}
//// {/*</div>|]*/}
//// </div>;
verify.toggleLineComment(
`const a = <div>
{/*<div>*/}
{/* SomeText*/}
{/*</div>*/}
</div>;
const b = <div>
<div>
SomeText
</div>
</div>;`);

View file

@ -0,0 +1,18 @@
// If at least one line is not commented then comment all lines again.
// TODO: Not sure about this one. The default behavior for line comment is to add en extra
// layer of comments (see toggleLineComment4 test). For jsx this doesn't work right as it's actually
// multiline comment. Figure out what to do.
//@Filename: file.tsx
//// const a = <div>
//// {/*[|<div>*/}
//// SomeText
//// {/*</div>|]*/}
//// </div>;
verify.toggleLineComment(
`const a = <div>
{/*<div>*/}
{/* SomeText*/}
{/*</div>*/}
</div>;`);

View file

@ -0,0 +1,30 @@
// Simple multiline comment and uncomment.
//// let var1[| = 1;
//// let var2 = 2;
//// let var3 |]= 3;
////
//// let var4/* = 1;
//// let var5 [||]= 2;
//// let var6 */= 3;
////
//// [|/*let var7 = 1;
//// let var8 = 2;
//// let var9 = 3;*/|]
////
//// let var10[||] = 10;
verify.toggleMultilineComment(
`let var1/* = 1;
let var2 = 2;
let var3 */= 3;
let var4 = 1;
let var5 = 2;
let var6 = 3;
let var7 = 1;
let var8 = 2;
let var9 = 3;
let var10/**/ = 10;`);

View file

@ -0,0 +1,35 @@
// If selection is outside of a multiline comment then insert comment
// instead of removing.
//// let var1/* = 1;
//// let var2 [|= 2;
//// let var3 */= 3;|]
////
//// [|let var4/* = 1;
//// let var5 |]= 2;
//// let var6 */= 3;
////
//// [|let var7/* = 1;
//// let var8 = 2;
//// let var9 */= 3;|]
////
//// /*let va[|r10 = 1;*/
//// let var11 = 2;
//// /*let var12|] = 3;*/
verify.toggleMultilineComment(
`let var1/* = 1;
let var2 *//*= 2;
let var3 *//*= 3;*/
/*let var4*//* = 1;
let var5 *//*= 2;
let var6 */= 3;
/*let var7*//* = 1;
let var8 = 2;
let var9 *//*= 3;*/
/*let va*//*r10 = 1;*//*
let var11 = 2;
*//*let var12*//* = 3;*/`);

View file

@ -0,0 +1,28 @@
/// <reference path="fourslash.ts">
// If range is inside a single line comment, just add the multiline comment.
//// // let va[|r1 = 1;
//// let var2 = 2;
//// // let var3|] = 3;
////
//// // let va[|r4 = 1;
//// let var5 = 2;
//// /* let var6|] = 3;*/
////
//// /* let va[|r7 = 1;*/
//// let var8 = 2;
//// // let var9|] = 3;
verify.toggleMultilineComment(
`/*// let var1 = 1;
let var2 = 2;
// let var3*/ = 3;
/*// let var4 = 1;
let var5 = 2;
*//* let var6*//* = 3;*/
/* let va*//*r7 = 1;*//*
let var8 = 2;
// let var9*/ = 3;`);

View file

@ -0,0 +1,7 @@
// This is an edgecase. The string contains a multiline comment syntax but it is a string
// and not actually a comment. When toggling it doesn't get escaped or appended comments.
// The result would be a portion of the selection to be "not commented".
//// /*let s[|omeLongVa*/riable = "Some other /*long th*/in|]g";
verify.toggleMultilineComment(`/*let s*//*omeLongVa*//*riable = "Some other /*long th*/in*/g";`);

View file

@ -0,0 +1,34 @@
// Jsx uses block comments for each line commented.
// Common JSX comment scenarios
//@Filename: file.tsx
//// const a = <div tabIndex="0">[|</div>;|]
//// const b = <div>This is [|valid HTML &amp;|] JSX at the same time.</div>;
//// const c = <MyContainer>
//// [|<MyFirstComponent />
//// <MySecondComponent />|]
//// </MyContainer>;
//// const d = <MyContainer>
//// <MyFirstComp[|onent />
//// <MySecondComponent />|]
//// </MyContainer>;
//// const e = <MyComponent>[|{'foo'}|]</MyComponent>;
//// const f = <div>Some text</div[|>;|]
//// const g = <div>Some text<[|/div>;|]
verify.toggleMultilineComment(
`const a = <div tabIndex="0">{/*</div>;*/}
const b = <div>This is {/*valid HTML &amp;*/} JSX at the same time.</div>;
const c = <MyContainer>
{/*<MyFirstComponent />
<MySecondComponent />*/}
</MyContainer>;
const d = <MyContainer>
<MyFirstComp{/*onent />
<MySecondComponent />*/}
</MyContainer>;
const e = <MyComponent>{/*{'foo'}*/}</MyComponent>;
const f = <div>Some text</div{/*>;*/}
const g = <div>Some text<{/*/div>;*/}`
);

View file

@ -0,0 +1,43 @@
// Jsx uses multiline comments for each line commented.
// Selection is outside of a multiline comments inserts multiline comments instead of removing.
// There's some variations between jsx and js comments depending on the position.
//@Filename: file.tsx
//// const var1 = <div>Tex{/*t1</div>;
//// const var2 = <div>Text2[|</div>;
//// const var3 = <div>Tex*/}t3</div>;|]
////
//// [|const var4 = <div>Tex{/*t4</div>;
//// const var5 = <div|]>Text5</div>;
//// const var6 = <div>Tex*/}t6</div>;
////
//// [|const var7 = <div>Tex{/*t7</div>;
//// const var8 = <div>Text8</div>;
//// const var9 = <div>Tex*/}t9</div>;|]
////
//// const var10 = <div>
//// {/*<div>T[|ext</div>*/}
//// <div>Text</div>
//// {/*<div>Text|]</div>*/}
//// </div>;
verify.toggleMultilineComment(
`const var1 = <div>Tex{/*t1</div>;
const var2 = <div>Text2*/}{/*</div>;
const var3 = <div>Tex*/}{/*t3</div>;*/}
/*const var4 = <div>Tex{*//*t4</div>;
const var5 = <div*//*>Text5</div>;
const var6 = <div>Tex*/}t6</div>;
/*const var7 = <div>Tex{*//*t7</div>;
const var8 = <div>Text8</div>;
const var9 = <div>Tex*//*}t9</div>;*/
const var10 = <div>
{/*<div>T*/}{/*ext</div>*/}{/*
<div>Text</div>
*/}{/*<div>Text*/}{/*</div>*/}
</div>;`
);

View file

@ -0,0 +1,33 @@
// Cases where the cursor is inside JSX like sintax but it's actually js.
//@Filename: file.tsx
//// const a = (
//// [|<div>
//// some text|]
//// </div>
//// );
//// const b = <MyComponent foo={1 [|+ 2 + 3 + 4|]} />;
//// const c = <MyComponent message={[|'hello world'|]} />;
//// const d = <MyTextBox autocomplete={tr[|ue|]} />;
//// const e = <MyCo[|mponent message={'hello world'} />;|]
//// const f = [
//// [|<li key="A">First item</li>,
//// <li key="B">Second item</li>,|]
//// <li key="C">Third item</li>,
//// ];
verify.toggleMultilineComment(
`const a = (
/*<div>
some text*/
</div>
);
const b = <MyComponent foo={1 /*+ 2 + 3 + 4*/} />;
const c = <MyComponent message={/*'hello world'*/} />;
const d = <MyTextBox autocomplete={tr/*ue*/} />;
const e = <MyCo/*mponent message={'hello world'} />;*/
const f = [
/*<li key="A">First item</li>,
<li key="B">Second item</li>,*/
<li key="C">Third item</li>,
];`);

View file

@ -0,0 +1,12 @@
// If the range only contains comments, uncomment all.
//// /*let var[|1 = 1;*/
//// /*let var2 = 2;*/
////
//// /*let var3 |]= 3;*/
verify.toggleMultilineComment(
`let var1 = 1;
let var2 = 2;
let var3 = 3;`);

View file

@ -0,0 +1,30 @@
// When there's is only whitespace, insert comment. If there is whitespace but theres a comment in bewteen, then uncomment.
//// /*let var1[| = 1;*/
//// |]
////
//// [|
//// /*let var2 = 2;*/|]
////
//// [|
////
//// |]
////
//// [||]
////
//// let var3[||] = 3;
verify.toggleMultilineComment(
`let var1 = 1;
let var2 = 2;
/*
*/
/**/
let var3/**/ = 3;`);

View file

@ -0,0 +1,42 @@
// Simple comment selection cases.
//// //let var1[| = 1;
//// //let var2 = 2;
//// //let var3 |]= 3;
////
//// //let var4[| = 4;
//// /*let var5 = 5;*/
//// //let var6 = 6;
////
//// let var7 |]= 7;
////
//// let var8/* = 1;
//// let var9 [||]= 2;
//// let var10 */= 3;
////
//// let var11[||]/* = 1;
//// let var12 = 2;
//// let var13 */= 3;
////
//// ////let var14 [||]= 14;
verify.uncommentSelection(
`let var1 = 1;
let var2 = 2;
let var3 = 3;
let var4 = 4;
let var5 = 5;
let var6 = 6;
let var7 = 7;
let var8 = 1;
let var9 = 2;
let var10 = 3;
let var11 = 1;
let var12 = 2;
let var13 = 3;
//let var14 = 14;`);

View file

@ -0,0 +1,34 @@
// Common uncomment jsx cases
//@Filename: file.tsx
//// const a = <MyContainer>
//// {/*<MyF[|irstComponent />*/}
//// {/*<MySec|]ondComponent />*/}
//// </MyContainer>;
////
//// const b = <div>
//// {/*[|<div>*/}
//// SomeText
//// {/*</div>|]*/}
//// </div>;
////
//// const c = <MyContainer>
//// [||]{/*<MyFirstComponent />*/}
//// </MyContainer>;
verify.uncommentSelection(
`const a = <MyContainer>
<MyFirstComponent />
<MySecondComponent />
</MyContainer>;
const b = <div>
<div>
SomeText
</div>
</div>;
const c = <MyContainer>
<MyFirstComponent />
</MyContainer>;`);

View file

@ -0,0 +1,34 @@
// Remove all comments within the selection
//// let var1/* = 1;
//// let var2 [|= 2;
//// let var3 */= 3;|]
////
//// [|let var4/* = 1;
//// let var5 |]= 2;
//// let var6 */= 3;
////
//// [|let var7/* = 1;
//// let var8 = 2;
//// let var9 */= 3;|]
////
//// /*let va[|r10 = 1;*/
//// let var11 = 2;
//// /*let var12|] = 3;*/
verify.uncommentSelection(
`let var1 = 1;
let var2 = 2;
let var3 = 3;
let var4 = 1;
let var5 = 2;
let var6 = 3;
let var7 = 1;
let var8 = 2;
let var9 = 3;
let var10 = 1;
let var11 = 2;
let var12 = 3;`);

View file

@ -0,0 +1,40 @@
// Remove all comments in jsx.
//@Filename: file.tsx
//// const var1 = <div>Tex{/*t1</div>;
//// const var2 = <div>Text2[|</div>;
//// const var3 = <div>Tex*/}t3</div>;|]
////
//// [|const var4 = <div>Tex{/*t4</div>;
//// const var5 = <div|]>Text5</div>;
//// const var6 = <div>Tex*/}t6</div>;
////
//// [|const var7 = <div>Tex{/*t7</div>;
//// const var8 = <div>Text8</div>;
//// const var9 = <div>Tex*/}t9</div>;|]
////
//// const var10 = <div>
//// {/*<div>T[|ext</div>*/}
//// <div>Text</div>
//// {/*<div>Text|]</div>*/}
//// </div>;
verify.uncommentSelection(
`const var1 = <div>Text1</div>;
const var2 = <div>Text2</div>;
const var3 = <div>Text3</div>;
const var4 = <div>Text4</div>;
const var5 = <div>Text5</div>;
const var6 = <div>Text6</div>;
const var7 = <div>Text7</div>;
const var8 = <div>Text8</div>;
const var9 = <div>Text9</div>;
const var10 = <div>
<div>Text</div>
<div>Text</div>
<div>Text</div>
</div>;`
);