Merge branch 'master' of https://github.com/Microsoft/TypeScript into feature/eslint

This commit is contained in:
Alexander T 2019-08-27 09:30:31 +03:00
commit acafb502cd
21 changed files with 559 additions and 194 deletions

View file

@ -134,11 +134,13 @@ namespace ts.server {
this.processRequest(CommandNames.Close, args);
}
changeFile(fileName: string, start: number, end: number, insertString: string): void {
createChangeFileRequestArgs(fileName: string, start: number, end: number, insertString: string): protocol.ChangeRequestArgs {
return { ...this.createFileLocationRequestArgsWithEndLineAndOffset(fileName, start, end), insertString };
}
changeFile(fileName: string, args: protocol.ChangeRequestArgs): void {
// clear the line map after an edit
this.lineMaps.set(fileName, undefined!); // TODO: GH#18217
const args: protocol.ChangeRequestArgs = { ...this.createFileLocationRequestArgsWithEndLineAndOffset(fileName, start, end), insertString };
this.processRequest(CommandNames.Change, args);
}

View file

@ -80,11 +80,11 @@ namespace FourSlash {
// To add additional option, add property into the testOptMetadataNames, refer the property in either globalMetadataNames or fileMetadataNames
// Add cases into convertGlobalOptionsToCompilationsSettings function for the compiler to acknowledge such option from meta data
const enum MetadataOptionNames {
baselineFile = "BaselineFile",
emitThisFile = "emitThisFile", // This flag is used for testing getEmitOutput feature. It allows test-cases to indicate what file to be output in multiple files project
fileName = "Filename",
resolveReference = "ResolveReference", // This flag is used to specify entry file for resolve file references. The flag is only allow once per test file
symlink = "Symlink",
baselineFile = "baselinefile",
emitThisFile = "emitthisfile", // This flag is used for testing getEmitOutput feature. It allows test-cases to indicate what file to be output in multiple files project
fileName = "filename",
resolveReference = "resolvereference", // This flag is used to specify entry file for resolve file references. The flag is only allow once per test file
symlink = "symlink",
}
// List of allowed metadata names
@ -150,6 +150,7 @@ namespace FourSlash {
private languageServiceAdapterHost: Harness.LanguageService.LanguageServiceAdapterHost;
private languageService: ts.LanguageService;
private cancellationToken: TestCancellationToken;
private assertTextConsistent: ((fileName: string) => void) | undefined;
// The current caret position in the active file
public currentCaretPosition = 0;
@ -280,6 +281,9 @@ namespace FourSlash {
const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions);
this.languageServiceAdapterHost = languageServiceAdapter.getHost();
this.languageService = memoWrap(languageServiceAdapter.getLanguageService(), this); // Wrap the LS to cache some expensive operations certain tests call repeatedly
if (this.testType === FourSlashTestType.Server) {
this.assertTextConsistent = fileName => (languageServiceAdapter as Harness.LanguageService.ServerLanguageServiceAdapter).assertTextConsistent(fileName);
}
if (startResolveFileRef) {
// Add the entry-point file itself into the languageServiceShimHost
@ -1867,6 +1871,9 @@ namespace FourSlash {
private editScriptAndUpdateMarkers(fileName: string, editStart: number, editEnd: number, newText: string) {
this.languageServiceAdapterHost.editScript(fileName, editStart, editEnd, newText);
if (this.assertTextConsistent) {
this.assertTextConsistent(fileName);
}
for (const marker of this.testData.markers) {
if (marker.fileName === fileName) {
marker.position = updatePosition(marker.position, editStart, editEnd, newText);
@ -3274,7 +3281,7 @@ ${code}
function parseTestData(basePath: string, contents: string, fileName: string): FourSlashData {
// Regex for parsing options in the format "@Alpha: Value of any sort"
const optionRegex = /^\s*@(\w+): (.*)\s*/;
const optionRegex = /^\s*@(\w+):\s*(.*)\s*/;
// List of all the subfiles we've parsed out
const files: FourSlashFile[] = [];
@ -3286,6 +3293,7 @@ ${code}
// Note: IE JS engine incorrectly handles consecutive delimiters here when using RegExp split, so
// we have to string-based splitting instead and try to figure out the delimiting chars
const lines = contents.split("\n");
let i = 0;
const markerPositions = ts.createMap<Marker>();
const markers: Marker[] = [];
@ -3314,6 +3322,7 @@ ${code}
}
for (let line of lines) {
i++;
if (line.length > 0 && line.charAt(line.length - 1) === "\r") {
line = line.substr(0, line.length - 1);
}
@ -3322,11 +3331,15 @@ ${code}
const text = line.substr(4);
currentFileContent = currentFileContent === undefined ? text : currentFileContent + "\n" + text;
}
else if (line.substr(0, 3) === "///" && currentFileContent !== undefined) {
throw new Error("Three-slash line in the middle of four-slash region at line " + i);
}
else if (line.substr(0, 2) === "//") {
// Comment line, check for global/file @options and record them
const match = optionRegex.exec(line.substr(2));
if (match) {
const [key, value] = match.slice(1);
const key = match[1].toLowerCase();
const value = match[2];
if (!ts.contains(fileMetadataNames, key)) {
// Check if the match is already existed in the global options
if (globalOptions[key] !== undefined) {

View file

@ -658,8 +658,9 @@ namespace Harness.LanguageService {
}
editScript(fileName: string, start: number, end: number, newText: string) {
const changeArgs = this.client.createChangeFileRequestArgs(fileName, start, end, newText);
super.editScript(fileName, start, end, newText);
this.client.changeFile(fileName, start, end, newText);
this.client.changeFile(fileName, changeArgs);
}
}
@ -716,8 +717,8 @@ namespace Harness.LanguageService {
return this.host.getCurrentDirectory();
}
getDirectories(): string[] {
return [];
getDirectories(path: string): string[] {
return this.host.getDirectories(path);
}
getEnvironmentVariable(name: string): string {
@ -890,9 +891,16 @@ namespace Harness.LanguageService {
}
}
class FourslashSession extends ts.server.Session {
getText(fileName: string) {
return ts.getSnapshotText(this.projectService.getDefaultProjectForFile(ts.server.toNormalizedPath(fileName), /*ensureProject*/ true)!.getScriptSnapshot(fileName)!);
}
}
export class ServerLanguageServiceAdapter implements LanguageServiceAdapter {
private host: SessionClientHost;
private client: ts.server.SessionClient;
private server: FourslashSession;
constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
// This is the main host that tests use to direct tests
const clientHost = new SessionClientHost(cancellationToken, options);
@ -912,11 +920,12 @@ namespace Harness.LanguageService {
logger: serverHost,
canUseEvents: true
};
const server = new ts.server.Session(opts);
this.server = new FourslashSession(opts);
// Fake the connection between the client and the server
serverHost.writeMessage = client.onMessage.bind(client);
clientHost.writeMessage = server.onMessage.bind(server);
clientHost.writeMessage = this.server.onMessage.bind(this.server);
// Wire the client to the host to get notifications when a file is open
// or edited.
@ -930,5 +939,20 @@ namespace Harness.LanguageService {
getLanguageService(): ts.LanguageService { return this.client; }
getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); }
getPreProcessedFileInfo(): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
assertTextConsistent(fileName: string) {
const serverText = this.server.getText(fileName);
const clientText = this.host.readFile(fileName);
ts.Debug.assert(serverText === clientText, [
"Server and client text are inconsistent.",
"",
"\x1b[1mServer\x1b[0m\x1b[31m:",
serverText,
"",
"\x1b[1mClient\x1b[0m\x1b[31m:",
clientText,
"",
"This probably means something is wrong with the fourslash infrastructure, not with the test."
].join(ts.sys.newLine));
}
}
}

View file

@ -771,6 +771,14 @@ namespace ts {
return false;
}
// Limiting classification to exactly the elements and attributes
// defined in `ts.commentPragmas` would be excessive, but we can avoid
// some obvious false positives (e.g. in XML-like doc comments) by
// checking the element name.
if (!match[3] || !(match[3] in commentPragmas)) {
return false;
}
let pos = start;
pushCommentRange(pos, match[1].length); // ///
@ -779,10 +787,6 @@ namespace ts {
pushClassification(pos, match[2].length, ClassificationType.punctuation); // <
pos += match[2].length;
if (!match[3]) {
return true;
}
pushClassification(pos, match[3].length, ClassificationType.jsxSelfClosingTagName); // element name
pos += match[3].length;

View file

@ -359,7 +359,7 @@ namespace ts.codefix {
const references = getReferences(token, program, cancellationToken);
const checker = program.getTypeChecker();
const types = InferFromReference.inferTypesFromReferences(references, checker, cancellationToken);
return InferFromReference.unifyFromContext(types, checker);
return InferFromReference.unifyFromUsage(types, checker);
}
function inferFunctionReferencesFromUsage(containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): readonly Identifier[] | undefined {
@ -395,33 +395,33 @@ namespace ts.codefix {
}
namespace InferFromReference {
interface CallContext {
interface CallUsage {
argumentTypes: Type[];
returnType: UsageContext;
returnType: Usage;
}
interface UsageContext {
interface Usage {
isNumber?: boolean;
isString?: boolean;
/** Used ambiguously, eg x + ___ or object[___]; results in string | number if no other evidence exists */
isNumberOrString?: boolean;
candidateTypes?: Type[];
properties?: UnderscoreEscapedMap<UsageContext>;
callContexts?: CallContext[];
constructContexts?: CallContext[];
numberIndexContext?: UsageContext;
stringIndexContext?: UsageContext;
properties?: UnderscoreEscapedMap<Usage>;
calls?: CallUsage[];
constructs?: CallUsage[];
numberIndex?: Usage;
stringIndex?: Usage;
candidateThisTypes?: Type[];
}
export function inferTypesFromReferences(references: readonly Identifier[], checker: TypeChecker, cancellationToken: CancellationToken): Type[] {
const usageContext: UsageContext = {};
const usage: Usage = {};
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
inferTypeFromContext(reference, checker, usageContext);
calculateUsageOfNode(reference, checker, usage);
}
return inferFromContext(usageContext, checker);
return inferFromUsage(usage, checker);
}
export function inferTypeForParametersFromReferences(references: readonly Identifier[] | undefined, declaration: FunctionLike, program: Program, cancellationToken: CancellationToken): ParameterInference[] | undefined {
@ -430,35 +430,35 @@ namespace ts.codefix {
}
const checker = program.getTypeChecker();
const usageContext: UsageContext = {};
const usage: Usage = {};
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
inferTypeFromContext(reference, checker, usageContext);
calculateUsageOfNode(reference, checker, usage);
}
const callContexts = [...usageContext.constructContexts || [], ...usageContext.callContexts || []];
const calls = [...usage.constructs || [], ...usage.calls || []];
return declaration.parameters.map((parameter, parameterIndex): ParameterInference => {
const types = [];
const isRest = isRestParameter(parameter);
let isOptional = false;
for (const callContext of callContexts) {
if (callContext.argumentTypes.length <= parameterIndex) {
for (const call of calls) {
if (call.argumentTypes.length <= parameterIndex) {
isOptional = isInJSFile(declaration);
types.push(checker.getUndefinedType());
}
else if (isRest) {
for (let i = parameterIndex; i < callContext.argumentTypes.length; i++) {
types.push(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[i]));
for (let i = parameterIndex; i < call.argumentTypes.length; i++) {
types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[i]));
}
}
else {
types.push(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[parameterIndex]));
types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex]));
}
}
if (isIdentifier(parameter.name)) {
const inferred = inferTypesFromReferences(getReferences(parameter.name, program, cancellationToken), checker, cancellationToken);
types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred));
}
const type = unifyFromContext(types, checker);
const type = unifyFromUsage(types, checker);
return {
type: isRest ? checker.createArrayType(type) : type,
isOptional: isOptional && !isRest,
@ -473,89 +473,89 @@ namespace ts.codefix {
}
const checker = program.getTypeChecker();
const usageContext: UsageContext = {};
const usage: Usage = {};
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
inferTypeFromContext(reference, checker, usageContext);
calculateUsageOfNode(reference, checker, usage);
}
return unifyFromContext(usageContext.candidateThisTypes || emptyArray, checker);
return unifyFromUsage(usage.candidateThisTypes || emptyArray, checker);
}
function inferTypeFromContext(node: Expression, checker: TypeChecker, usageContext: UsageContext): void {
function calculateUsageOfNode(node: Expression, checker: TypeChecker, usage: Usage): void {
while (isRightSideOfQualifiedNameOrPropertyAccess(node)) {
node = <Expression>node.parent;
}
switch (node.parent.kind) {
case SyntaxKind.PostfixUnaryExpression:
usageContext.isNumber = true;
usage.isNumber = true;
break;
case SyntaxKind.PrefixUnaryExpression:
inferTypeFromPrefixUnaryExpressionContext(<PrefixUnaryExpression>node.parent, usageContext);
inferTypeFromPrefixUnaryExpression(<PrefixUnaryExpression>node.parent, usage);
break;
case SyntaxKind.BinaryExpression:
inferTypeFromBinaryExpressionContext(node, <BinaryExpression>node.parent, checker, usageContext);
inferTypeFromBinaryExpression(node, <BinaryExpression>node.parent, checker, usage);
break;
case SyntaxKind.CaseClause:
case SyntaxKind.DefaultClause:
inferTypeFromSwitchStatementLabelContext(<CaseOrDefaultClause>node.parent, checker, usageContext);
inferTypeFromSwitchStatementLabel(<CaseOrDefaultClause>node.parent, checker, usage);
break;
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
if ((<CallExpression | NewExpression>node.parent).expression === node) {
inferTypeFromCallExpressionContext(<CallExpression | NewExpression>node.parent, checker, usageContext);
inferTypeFromCallExpression(<CallExpression | NewExpression>node.parent, checker, usage);
}
else {
inferTypeFromContextualType(node, checker, usageContext);
inferTypeFromContextualType(node, checker, usage);
}
break;
case SyntaxKind.PropertyAccessExpression:
inferTypeFromPropertyAccessExpressionContext(<PropertyAccessExpression>node.parent, checker, usageContext);
inferTypeFromPropertyAccessExpression(<PropertyAccessExpression>node.parent, checker, usage);
break;
case SyntaxKind.ElementAccessExpression:
inferTypeFromPropertyElementExpressionContext(<ElementAccessExpression>node.parent, node, checker, usageContext);
inferTypeFromPropertyElementExpression(<ElementAccessExpression>node.parent, node, checker, usage);
break;
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
inferTypeFromPropertyAssignment(<PropertyAssignment | ShorthandPropertyAssignment>node.parent, checker, usageContext);
inferTypeFromPropertyAssignment(<PropertyAssignment | ShorthandPropertyAssignment>node.parent, checker, usage);
break;
case SyntaxKind.PropertyDeclaration:
inferTypeFromPropertyDeclaration(<PropertyDeclaration>node.parent, checker, usageContext);
inferTypeFromPropertyDeclaration(<PropertyDeclaration>node.parent, checker, usage);
break;
case SyntaxKind.VariableDeclaration: {
const { name, initializer } = node.parent as VariableDeclaration;
if (node === name) {
if (initializer) { // This can happen for `let x = null;` which still has an implicit-any error.
addCandidateType(usageContext, checker.getTypeAtLocation(initializer));
addCandidateType(usage, checker.getTypeAtLocation(initializer));
}
break;
}
}
// falls through
default:
return inferTypeFromContextualType(node, checker, usageContext);
return inferTypeFromContextualType(node, checker, usage);
}
}
function inferTypeFromContextualType(node: Expression, checker: TypeChecker, usageContext: UsageContext): void {
function inferTypeFromContextualType(node: Expression, checker: TypeChecker, usage: Usage): void {
if (isExpressionNode(node)) {
addCandidateType(usageContext, checker.getContextualType(node));
addCandidateType(usage, checker.getContextualType(node));
}
}
function inferTypeFromPrefixUnaryExpressionContext(node: PrefixUnaryExpression, usageContext: UsageContext): void {
function inferTypeFromPrefixUnaryExpression(node: PrefixUnaryExpression, usage: Usage): void {
switch (node.operator) {
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
case SyntaxKind.MinusToken:
case SyntaxKind.TildeToken:
usageContext.isNumber = true;
usage.isNumber = true;
break;
case SyntaxKind.PlusToken:
usageContext.isNumberOrString = true;
usage.isNumberOrString = true;
break;
// case SyntaxKind.ExclamationToken:
@ -563,7 +563,7 @@ namespace ts.codefix {
}
}
function inferTypeFromBinaryExpressionContext(node: Expression, parent: BinaryExpression, checker: TypeChecker, usageContext: UsageContext): void {
function inferTypeFromBinaryExpression(node: Expression, parent: BinaryExpression, checker: TypeChecker, usage: Usage): void {
switch (parent.operatorToken.kind) {
// ExponentiationOperator
case SyntaxKind.AsteriskAsteriskToken:
@ -612,10 +612,10 @@ namespace ts.codefix {
case SyntaxKind.GreaterThanEqualsToken:
const operandType = checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left);
if (operandType.flags & TypeFlags.EnumLike) {
addCandidateType(usageContext, operandType);
addCandidateType(usage, operandType);
}
else {
usageContext.isNumber = true;
usage.isNumber = true;
}
break;
@ -623,16 +623,16 @@ namespace ts.codefix {
case SyntaxKind.PlusToken:
const otherOperandType = checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left);
if (otherOperandType.flags & TypeFlags.EnumLike) {
addCandidateType(usageContext, otherOperandType);
addCandidateType(usage, otherOperandType);
}
else if (otherOperandType.flags & TypeFlags.NumberLike) {
usageContext.isNumber = true;
usage.isNumber = true;
}
else if (otherOperandType.flags & TypeFlags.StringLike) {
usageContext.isString = true;
usage.isString = true;
}
else {
usageContext.isNumberOrString = true;
usage.isNumberOrString = true;
}
break;
@ -642,12 +642,12 @@ namespace ts.codefix {
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
addCandidateType(usageContext, checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left));
addCandidateType(usage, checker.getTypeAtLocation(parent.left === node ? parent.right : parent.left));
break;
case SyntaxKind.InKeyword:
if (node === parent.left) {
usageContext.isString = true;
usage.isString = true;
}
break;
@ -657,7 +657,7 @@ namespace ts.codefix {
(node.parent.parent.kind === SyntaxKind.VariableDeclaration || isAssignmentExpression(node.parent.parent, /*excludeCompoundAssignment*/ true))) {
// var x = x || {};
// TODO: use getFalsyflagsOfType
addCandidateType(usageContext, checker.getTypeAtLocation(parent.right));
addCandidateType(usage, checker.getTypeAtLocation(parent.right));
}
break;
@ -669,72 +669,68 @@ namespace ts.codefix {
}
}
function inferTypeFromSwitchStatementLabelContext(parent: CaseOrDefaultClause, checker: TypeChecker, usageContext: UsageContext): void {
addCandidateType(usageContext, checker.getTypeAtLocation(parent.parent.parent.expression));
function inferTypeFromSwitchStatementLabel(parent: CaseOrDefaultClause, checker: TypeChecker, usage: Usage): void {
addCandidateType(usage, checker.getTypeAtLocation(parent.parent.parent.expression));
}
function inferTypeFromCallExpressionContext(parent: CallExpression | NewExpression, checker: TypeChecker, usageContext: UsageContext): void {
const callContext: CallContext = {
function inferTypeFromCallExpression(parent: CallExpression | NewExpression, checker: TypeChecker, usage: Usage): void {
const call: CallUsage = {
argumentTypes: [],
returnType: {}
};
if (parent.arguments) {
for (const argument of parent.arguments) {
callContext.argumentTypes.push(checker.getTypeAtLocation(argument));
call.argumentTypes.push(checker.getTypeAtLocation(argument));
}
}
inferTypeFromContext(parent, checker, callContext.returnType);
calculateUsageOfNode(parent, checker, call.returnType);
if (parent.kind === SyntaxKind.CallExpression) {
(usageContext.callContexts || (usageContext.callContexts = [])).push(callContext);
(usage.calls || (usage.calls = [])).push(call);
}
else {
(usageContext.constructContexts || (usageContext.constructContexts = [])).push(callContext);
(usage.constructs || (usage.constructs = [])).push(call);
}
}
function inferTypeFromPropertyAccessExpressionContext(parent: PropertyAccessExpression, checker: TypeChecker, usageContext: UsageContext): void {
function inferTypeFromPropertyAccessExpression(parent: PropertyAccessExpression, checker: TypeChecker, usage: Usage): void {
const name = escapeLeadingUnderscores(parent.name.text);
if (!usageContext.properties) {
usageContext.properties = createUnderscoreEscapedMap<UsageContext>();
if (!usage.properties) {
usage.properties = createUnderscoreEscapedMap<Usage>();
}
const propertyUsageContext = usageContext.properties.get(name) || { };
inferTypeFromContext(parent, checker, propertyUsageContext);
usageContext.properties.set(name, propertyUsageContext);
const propertyUsage = usage.properties.get(name) || { };
calculateUsageOfNode(parent, checker, propertyUsage);
usage.properties.set(name, propertyUsage);
}
function inferTypeFromPropertyElementExpressionContext(parent: ElementAccessExpression, node: Expression, checker: TypeChecker, usageContext: UsageContext): void {
function inferTypeFromPropertyElementExpression(parent: ElementAccessExpression, node: Expression, checker: TypeChecker, usage: Usage): void {
if (node === parent.argumentExpression) {
usageContext.isNumberOrString = true;
usage.isNumberOrString = true;
return;
}
else {
const indexType = checker.getTypeAtLocation(parent.argumentExpression);
const indexUsageContext = {};
inferTypeFromContext(parent, checker, indexUsageContext);
const indexUsage = {};
calculateUsageOfNode(parent, checker, indexUsage);
if (indexType.flags & TypeFlags.NumberLike) {
usageContext.numberIndexContext = indexUsageContext;
usage.numberIndex = indexUsage;
}
else {
usageContext.stringIndexContext = indexUsageContext;
usage.stringIndex = indexUsage;
}
}
}
function inferTypeFromPropertyAssignment(assignment: PropertyAssignment | ShorthandPropertyAssignment, checker: TypeChecker, usageContext: UsageContext) {
const objectLiteral = isShorthandPropertyAssignment(assignment) ?
assignment.parent :
assignment.parent.parent;
const nodeWithRealType = isVariableDeclaration(objectLiteral.parent) ?
objectLiteral.parent :
objectLiteral;
addCandidateThisType(usageContext, checker.getTypeAtLocation(nodeWithRealType));
function inferTypeFromPropertyAssignment(assignment: PropertyAssignment | ShorthandPropertyAssignment, checker: TypeChecker, usage: Usage) {
const nodeWithRealType = isVariableDeclaration(assignment.parent.parent) ?
assignment.parent.parent :
assignment.parent;
addCandidateThisType(usage, checker.getTypeAtLocation(nodeWithRealType));
}
function inferTypeFromPropertyDeclaration(declaration: PropertyDeclaration, checker: TypeChecker, usageContext: UsageContext) {
addCandidateThisType(usageContext, checker.getTypeAtLocation(declaration.parent));
function inferTypeFromPropertyDeclaration(declaration: PropertyDeclaration, checker: TypeChecker, usage: Usage) {
addCandidateThisType(usage, checker.getTypeAtLocation(declaration.parent));
}
interface Priority {
@ -755,7 +751,7 @@ namespace ts.codefix {
return inferences.filter(i => toRemove.every(f => !f(i)));
}
export function unifyFromContext(inferences: readonly Type[], checker: TypeChecker, fallback = checker.getAnyType()): Type {
export function unifyFromUsage(inferences: readonly Type[], checker: TypeChecker, fallback = checker.getAnyType()): Type {
if (!inferences.length) return fallback;
// 1. string or number individually override string | number
@ -825,82 +821,82 @@ namespace ts.codefix {
numberIndices.length ? checker.createIndexInfo(checker.getUnionType(numberIndices), numberIndexReadonly) : undefined);
}
function inferFromContext(usageContext: UsageContext, checker: TypeChecker) {
function inferFromUsage(usage: Usage, checker: TypeChecker) {
const types = [];
if (usageContext.isNumber) {
if (usage.isNumber) {
types.push(checker.getNumberType());
}
if (usageContext.isString) {
if (usage.isString) {
types.push(checker.getStringType());
}
if (usageContext.isNumberOrString) {
if (usage.isNumberOrString) {
types.push(checker.getUnionType([checker.getStringType(), checker.getNumberType()]));
}
types.push(...(usageContext.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t)));
types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t)));
if (usageContext.properties && hasCallContext(usageContext.properties.get("then" as __String))) {
const paramType = getParameterTypeFromCallContexts(0, usageContext.properties.get("then" as __String)!.callContexts!, /*isRestParameter*/ false, checker)!; // TODO: GH#18217
if (usage.properties && hasCalls(usage.properties.get("then" as __String))) {
const paramType = getParameterTypeFromCalls(0, usage.properties.get("then" as __String)!.calls!, /*isRestParameter*/ false, checker)!; // TODO: GH#18217
const types = paramType.getCallSignatures().map(c => c.getReturnType());
types.push(checker.createPromiseType(types.length ? checker.getUnionType(types, UnionReduction.Subtype) : checker.getAnyType()));
}
else if (usageContext.properties && hasCallContext(usageContext.properties.get("push" as __String))) {
types.push(checker.createArrayType(getParameterTypeFromCallContexts(0, usageContext.properties.get("push" as __String)!.callContexts!, /*isRestParameter*/ false, checker)!));
else if (usage.properties && hasCalls(usage.properties.get("push" as __String))) {
types.push(checker.createArrayType(getParameterTypeFromCalls(0, usage.properties.get("push" as __String)!.calls!, /*isRestParameter*/ false, checker)!));
}
if (usageContext.numberIndexContext) {
types.push(checker.createArrayType(recur(usageContext.numberIndexContext)));
if (usage.numberIndex) {
types.push(checker.createArrayType(recur(usage.numberIndex)));
}
else if (usageContext.properties || usageContext.callContexts || usageContext.constructContexts || usageContext.stringIndexContext) {
else if (usage.properties || usage.calls || usage.constructs || usage.stringIndex) {
const members = createUnderscoreEscapedMap<Symbol>();
const callSignatures: Signature[] = [];
const constructSignatures: Signature[] = [];
let stringIndexInfo: IndexInfo | undefined;
if (usageContext.properties) {
usageContext.properties.forEach((context, name) => {
if (usage.properties) {
usage.properties.forEach((u, name) => {
const symbol = checker.createSymbol(SymbolFlags.Property, name);
symbol.type = recur(context);
symbol.type = recur(u);
members.set(name, symbol);
});
}
if (usageContext.callContexts) {
for (const callContext of usageContext.callContexts) {
callSignatures.push(getSignatureFromCallContext(callContext, checker));
if (usage.calls) {
for (const call of usage.calls) {
callSignatures.push(getSignatureFromCall(call, checker));
}
}
if (usageContext.constructContexts) {
for (const constructContext of usageContext.constructContexts) {
constructSignatures.push(getSignatureFromCallContext(constructContext, checker));
if (usage.constructs) {
for (const construct of usage.constructs) {
constructSignatures.push(getSignatureFromCall(construct, checker));
}
}
if (usageContext.stringIndexContext) {
stringIndexInfo = checker.createIndexInfo(recur(usageContext.stringIndexContext), /*isReadonly*/ false);
if (usage.stringIndex) {
stringIndexInfo = checker.createIndexInfo(recur(usage.stringIndex), /*isReadonly*/ false);
}
types.push(checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined)); // TODO: GH#18217
}
return types;
function recur(innerContext: UsageContext): Type {
return unifyFromContext(inferFromContext(innerContext, checker), checker);
function recur(innerUsage: Usage): Type {
return unifyFromUsage(inferFromUsage(innerUsage, checker), checker);
}
}
function getParameterTypeFromCallContexts(parameterIndex: number, callContexts: CallContext[], isRestParameter: boolean, checker: TypeChecker) {
function getParameterTypeFromCalls(parameterIndex: number, calls: CallUsage[], isRestParameter: boolean, checker: TypeChecker) {
let types: Type[] = [];
if (callContexts) {
for (const callContext of callContexts) {
if (callContext.argumentTypes.length > parameterIndex) {
if (calls) {
for (const call of calls) {
if (call.argumentTypes.length > parameterIndex) {
if (isRestParameter) {
types = concatenate(types, map(callContext.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a)));
types = concatenate(types, map(call.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a)));
}
else {
types.push(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[parameterIndex]));
types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex]));
}
}
}
@ -913,32 +909,32 @@ namespace ts.codefix {
return undefined;
}
function getSignatureFromCallContext(callContext: CallContext, checker: TypeChecker): Signature {
function getSignatureFromCall(call: CallUsage, checker: TypeChecker): Signature {
const parameters: Symbol[] = [];
for (let i = 0; i < callContext.argumentTypes.length; i++) {
for (let i = 0; i < call.argumentTypes.length; i++) {
const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`));
symbol.type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(callContext.argumentTypes[i]));
symbol.type = checker.getWidenedType(checker.getBaseTypeOfLiteralType(call.argumentTypes[i]));
parameters.push(symbol);
}
const returnType = unifyFromContext(inferFromContext(callContext.returnType, checker), checker, checker.getVoidType());
const returnType = unifyFromUsage(inferFromUsage(call.returnType, checker), checker, checker.getVoidType());
// TODO: GH#18217
return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, callContext.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, call.argumentTypes.length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
}
function addCandidateType(context: UsageContext, type: Type | undefined) {
function addCandidateType(usage: Usage, type: Type | undefined) {
if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) {
(context.candidateTypes || (context.candidateTypes = [])).push(type);
(usage.candidateTypes || (usage.candidateTypes = [])).push(type);
}
}
function addCandidateThisType(context: UsageContext, type: Type | undefined) {
function addCandidateThisType(usage: Usage, type: Type | undefined) {
if (type && !(type.flags & TypeFlags.Any) && !(type.flags & TypeFlags.Never)) {
(context.candidateThisTypes || (context.candidateThisTypes = [])).push(type);
(usage.candidateThisTypes || (usage.candidateThisTypes = [])).push(type);
}
}
function hasCallContext(usageContext: UsageContext | undefined): boolean {
return !!usageContext && !!usageContext.callContexts;
function hasCalls(usage: Usage | undefined): boolean {
return !!usage && !!usage.calls;
}
}
}

View file

@ -9,8 +9,8 @@ namespace ts.Completions {
}
export type Log = (message: string) => void;
const enum SymbolOriginInfoKind { ThisType, SymbolMemberNoExport, SymbolMemberExport, Export }
type SymbolOriginInfo = { kind: SymbolOriginInfoKind.ThisType } | { kind: SymbolOriginInfoKind.SymbolMemberNoExport } | SymbolOriginInfoExport;
const enum SymbolOriginInfoKind { ThisType, SymbolMemberNoExport, SymbolMemberExport, Export, Promise }
type SymbolOriginInfo = { kind: SymbolOriginInfoKind.ThisType } | { kind: SymbolOriginInfoKind.Promise } | { kind: SymbolOriginInfoKind.SymbolMemberNoExport } | SymbolOriginInfoExport;
interface SymbolOriginInfoExport {
kind: SymbolOriginInfoKind.SymbolMemberExport | SymbolOriginInfoKind.Export;
moduleSymbol: Symbol;
@ -22,6 +22,9 @@ namespace ts.Completions {
function originIsExport(origin: SymbolOriginInfo): origin is SymbolOriginInfoExport {
return origin.kind === SymbolOriginInfoKind.SymbolMemberExport || origin.kind === SymbolOriginInfoKind.Export;
}
function originIsPromise(origin: SymbolOriginInfo): boolean {
return origin.kind === SymbolOriginInfoKind.Promise;
}
/**
* Map from symbol id -> SymbolOriginInfo.
@ -264,6 +267,12 @@ namespace ts.Completions {
replacementSpan = createTextSpanFromNode(isJsxInitializer, sourceFile);
}
}
if (origin && originIsPromise(origin) && propertyAccessToConvert) {
if (insertText === undefined) insertText = name;
const awaitText = `(await ${propertyAccessToConvert.expression.getText()})`;
insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}.${insertText}`;
replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end);
}
if (insertText !== undefined && !preferences.includeCompletionsWithInsertText) {
return undefined;
@ -313,7 +322,7 @@ namespace ts.Completions {
log: Log,
kind: CompletionKind,
preferences: UserPreferences,
propertyAccessToConvert?: PropertyAccessExpression | undefined,
propertyAccessToConvert?: PropertyAccessExpression,
isJsxInitializer?: IsJsxInitializer,
recommendedCompletion?: Symbol,
symbolToOriginInfoMap?: SymbolOriginInfoMap,
@ -984,7 +993,7 @@ namespace ts.Completions {
if (!isTypeLocation &&
symbol.declarations &&
symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) {
addTypeProperties(typeChecker.getTypeOfSymbolAtLocation(symbol, node));
addTypeProperties(typeChecker.getTypeOfSymbolAtLocation(symbol, node), !!(node.flags & NodeFlags.AwaitContext));
}
return;
@ -999,13 +1008,14 @@ namespace ts.Completions {
}
if (!isTypeLocation) {
addTypeProperties(typeChecker.getTypeAtLocation(node));
addTypeProperties(typeChecker.getTypeAtLocation(node), !!(node.flags & NodeFlags.AwaitContext));
}
}
function addTypeProperties(type: Type): void {
function addTypeProperties(type: Type, insertAwait?: boolean): void {
isNewIdentifierLocation = !!type.getStringIndexType();
const propertyAccess = node.kind === SyntaxKind.ImportType ? <ImportTypeNode>node : <PropertyAccessExpression | QualifiedName>node.parent;
if (isUncheckedFile) {
// In javascript files, for union types, we don't just get the members that
// the individual types have in common, we also include all the members that
@ -1016,14 +1026,25 @@ namespace ts.Completions {
}
else {
for (const symbol of type.getApparentProperties()) {
if (typeChecker.isValidPropertyAccessForCompletions(node.kind === SyntaxKind.ImportType ? <ImportTypeNode>node : <PropertyAccessExpression | QualifiedName>node.parent, type, symbol)) {
if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) {
addPropertySymbol(symbol);
}
}
}
if (insertAwait && preferences.includeCompletionsWithInsertText) {
const promiseType = typeChecker.getPromisedTypeOfPromise(type);
if (promiseType) {
for (const symbol of promiseType.getApparentProperties()) {
if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol)) {
addPropertySymbol(symbol, /* insertAwait */ true);
}
}
}
}
}
function addPropertySymbol(symbol: Symbol) {
function addPropertySymbol(symbol: Symbol, insertAwait?: boolean) {
// For a computed property with an accessible name like `Symbol.iterator`,
// we'll add a completion for the *name* `Symbol` instead of for the property.
// If this is e.g. [Symbol.iterator], add a completion for `Symbol`.
@ -1040,12 +1061,20 @@ namespace ts.Completions {
!moduleSymbol || !isExternalModuleSymbol(moduleSymbol) ? { kind: SymbolOriginInfoKind.SymbolMemberNoExport } : { kind: SymbolOriginInfoKind.SymbolMemberExport, moduleSymbol, isDefaultExport: false };
}
else if (preferences.includeCompletionsWithInsertText) {
addPromiseSymbolOriginInfo(symbol);
symbols.push(symbol);
}
}
else {
addPromiseSymbolOriginInfo(symbol);
symbols.push(symbol);
}
function addPromiseSymbolOriginInfo (symbol: Symbol) {
if (insertAwait && preferences.includeCompletionsWithInsertText && !symbolToOriginInfoMap[getSymbolId(symbol)]) {
symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.Promise };
}
}
}
/** Given 'a.b.c', returns 'a'. */

View file

@ -15,6 +15,12 @@ namespace ts.NavigationBar {
*/
const whiteSpaceRegex = /\s+/g;
/**
* Maximum amount of characters to return
* The amount was choosen arbitrarily.
*/
const maxLength = 150;
// Keep sourceFile handy so we don't have to search for it every time we need to call `getText`.
let curCancellationToken: CancellationToken;
let curSourceFile: SourceFile;
@ -74,7 +80,7 @@ namespace ts.NavigationBar {
}
function nodeText(node: Node): string {
return node.getText(curSourceFile);
return cleanText(node.getText(curSourceFile));
}
function navigationBarNodeKind(n: NavigationBarNode): SyntaxKind {
@ -194,7 +200,7 @@ namespace ts.NavigationBar {
// Handle named bindings in imports e.g.:
// import * as NS from "mod";
// import {a, b as B} from "mod";
const {namedBindings} = importClause;
const { namedBindings } = importClause;
if (namedBindings) {
if (namedBindings.kind === SyntaxKind.NamespaceImport) {
addLeafNode(namedBindings);
@ -434,13 +440,13 @@ namespace ts.NavigationBar {
function getItemName(node: Node, name: Node | undefined): string {
if (node.kind === SyntaxKind.ModuleDeclaration) {
return getModuleName(<ModuleDeclaration>node);
return cleanText(getModuleName(<ModuleDeclaration>node));
}
if (name) {
const text = nodeText(name);
if (text.length > 0) {
return text;
return cleanText(text);
}
}
@ -636,11 +642,11 @@ namespace ts.NavigationBar {
function getFunctionOrClassName(node: FunctionExpression | FunctionDeclaration | ArrowFunction | ClassLikeDeclaration): string {
const { parent } = node;
if (node.name && getFullWidth(node.name) > 0) {
return declarationNameToString(node.name);
return cleanText(declarationNameToString(node.name));
}
// See if it is a var initializer. If so, use the var name.
else if (isVariableDeclaration(parent)) {
return declarationNameToString(parent.name);
return cleanText(declarationNameToString(parent.name));
}
// See if it is of the form "<expr> = function(){...}". If so, use the text from the left-hand side.
else if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken) {
@ -658,9 +664,15 @@ namespace ts.NavigationBar {
return "<class>";
}
else if (isCallExpression(parent)) {
const name = getCalledExpressionName(parent.expression);
let name = getCalledExpressionName(parent.expression);
if (name !== undefined) {
const args = mapDefined(parent.arguments, a => isStringLiteralLike(a) ? a.getText(curSourceFile) : undefined).join(", ");
name = cleanText(name);
if (name.length > maxLength) {
return `${name} callback`;
}
const args = cleanText(mapDefined(parent.arguments, a => isStringLiteralLike(a) ? a.getText(curSourceFile) : undefined).join(", "));
return `${name}(${args}) callback`;
}
}
@ -691,4 +703,16 @@ namespace ts.NavigationBar {
return false;
}
}
function cleanText(text: string): string {
// Truncate to maximum amount of characters as we don't want to do a big replace operation.
text = text.length > maxLength ? text.substring(0, maxLength) + "..." : text;
// Replaces ECMAScript line terminators and removes the trailing `\` from each line:
// \n - Line Feed
// \r - Carriage Return
// \u2028 - Line separator
// \u2029 - Paragraph separator
return text.replace(/\\?(\r?\n|\r|\u2028|\u2029)/g, "");
}
}

View file

@ -0,0 +1,18 @@
/// <reference path="fourslash.ts"/>
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @filename:destruct.js
//// function [|formatter|](message) {
//// const { type } = false ? { type: message } : message;
//// }
verify.codeFix({
description: "Infer parameter types from usage",
index: 0,
newFileContent: `/**
* @param {{ type: any; }} message
*/
function formatter(message) {
const { type } = false ? { type: message } : message;
}`
});

View file

@ -5,9 +5,6 @@
// @module: es2015
// @moduleResolution: node
// @Filename: /node_modules/@types/react/index.d.ts
////export = React;
////export as namespace React;
////declare namespace React {
//// export class Component { render(): JSX.Element | null; }
////}
@ -16,10 +13,6 @@
//// interface Element {}
//// }
////}
// @filename: a.tsx
//// import React from 'react';
////
//// export default function Component([|props |]) {
//// if (props.isLoading) {
@ -37,4 +30,4 @@
//// }
verify.rangeAfterCodeFix("props: { isLoading: any; update: (arg0: any) => void; }",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);
verify.rangeAfterCodeFix("props: { isLoading: any; update: (arg0: any) => void; }",/*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts'/>
//// async function foo(x: Promise<string>) {
//// [|x./**/|]
//// }
const replacementSpan = test.ranges()[0]
verify.completions({
marker: "",
includes: [
"then",
{ name: "trim", insertText: '(await x).trim', replacementSpan },
],
preferences: {
includeInsertTextCompletions: true,
},
});

View file

@ -0,0 +1,18 @@
/// <reference path='fourslash.ts'/>
//// interface Foo { foo: string }
//// async function foo(x: Promise<Foo>) {
//// [|x./**/|]
//// }
const replacementSpan = test.ranges()[0]
verify.completions({
marker: "",
includes: [
"then",
{ name: "foo", insertText: '(await x).foo', replacementSpan },
],
preferences: {
includeInsertTextCompletions: true,
},
});

View file

@ -0,0 +1,18 @@
/// <reference path='fourslash.ts'/>
//// interface Foo { ["foo-foo"]: string }
//// async function foo(x: Promise<Foo>) {
//// [|x./**/|]
//// }
const replacementSpan = test.ranges()[0]
verify.completions({
marker: "",
includes: [
"then",
{ name: "foo-foo", insertText: '(await x)["foo-foo"]', replacementSpan, },
],
preferences: {
includeInsertTextCompletions: true,
},
});

View file

@ -0,0 +1,15 @@
/// <reference path='fourslash.ts'/>
//// function foo(x: Promise<string>) {
//// [|x./**/|]
//// }
const replacementSpan = test.ranges()[0]
verify.completions({
marker: "",
includes: ["then"],
excludes: ["trim"],
preferences: {
includeInsertTextCompletions: true,
},
});

View file

@ -0,0 +1,18 @@
/// <reference path='fourslash.ts'/>
//// interface Foo { foo: string }
//// async function foo(x: (a: number) => Promise<Foo>) {
//// [|x(1)./**/|]
//// }
const replacementSpan = test.ranges()[0]
verify.completions({
marker: "",
includes: [
"then",
{ name: "foo", insertText: '(await x(1)).foo', replacementSpan },
],
preferences: {
includeInsertTextCompletions: true,
},
});

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts'/>
//// async function foo(x: Promise<string>) {
//// [|x./**/|]
//// }
const replacementSpan = test.ranges()[0]
verify.completions({
marker: "",
exact: [
"then",
"catch"
],
preferences: {
includeInsertTextCompletions: false,
},
});

View file

@ -7,6 +7,10 @@
////}
////declare module "MultilineMadness" {}
////
////declare module "Multiline\
////Madness2" {
////}
////
////interface Foo {
//// "a1\\\r\nb";
//// "a2\
@ -29,7 +33,12 @@ verify.navigationTree({
"kind": "script",
"childItems": [
{
"text": "\"Multiline\\\nMadness\"",
"text": "\"MultilineMadness\"",
"kind": "module",
"kindModifiers": "declare"
},
{
"text": "\"MultilineMadness2\"",
"kind": "module",
"kindModifiers": "declare"
},
@ -38,11 +47,6 @@ verify.navigationTree({
"kind": "module",
"kindModifiers": "declare"
},
{
"text": "\"MultilineMadness\"",
"kind": "module",
"kindModifiers": "declare"
},
{
"text": "Bar",
"kind": "class",
@ -52,7 +56,7 @@ verify.navigationTree({
"kind": "property"
},
{
"text": "'a2\\\n \\\n b'",
"text": "'a2 b'",
"kind": "method"
}
]
@ -66,7 +70,7 @@ verify.navigationTree({
"kind": "property"
},
{
"text": "\"a2\\\n \\\n b\"",
"text": "\"a2 b\"",
"kind": "method"
}
]
@ -80,7 +84,12 @@ verify.navigationBar([
"kind": "script",
"childItems": [
{
"text": "\"Multiline\\\nMadness\"",
"text": "\"MultilineMadness\"",
"kind": "module",
"kindModifiers": "declare"
},
{
"text": "\"MultilineMadness2\"",
"kind": "module",
"kindModifiers": "declare"
},
@ -89,11 +98,6 @@ verify.navigationBar([
"kind": "module",
"kindModifiers": "declare"
},
{
"text": "\"MultilineMadness\"",
"kind": "module",
"kindModifiers": "declare"
},
{
"text": "Bar",
"kind": "class"
@ -105,7 +109,13 @@ verify.navigationBar([
]
},
{
"text": "\"Multiline\\\nMadness\"",
"text": "\"MultilineMadness\"",
"kind": "module",
"kindModifiers": "declare",
"indent": 1
},
{
"text": "\"MultilineMadness2\"",
"kind": "module",
"kindModifiers": "declare",
"indent": 1
@ -116,12 +126,6 @@ verify.navigationBar([
"kindModifiers": "declare",
"indent": 1
},
{
"text": "\"MultilineMadness\"",
"kind": "module",
"kindModifiers": "declare",
"indent": 1
},
{
"text": "Bar",
"kind": "class",
@ -131,7 +135,7 @@ verify.navigationBar([
"kind": "property"
},
{
"text": "'a2\\\n \\\n b'",
"text": "'a2 b'",
"kind": "method"
}
],
@ -146,7 +150,7 @@ verify.navigationBar([
"kind": "property"
},
{
"text": "\"a2\\\n \\\n b\"",
"text": "\"a2 b\"",
"kind": "method"
}
],

View file

@ -0,0 +1,102 @@
//// function f(p1: () => any, p2: string) { }
//// f(() => { }, `line1\
//// line2\
//// line3`);
////
//// class c1 {
//// const a = ' ''line1\
//// line2';
//// }
////
//// f(() => { }, `unterminated backtick 1
//// unterminated backtick 2
//// unterminated backtick 3
verify.navigationTree({
"text": "<global>",
"kind": "script",
"childItems": [
{
"text": "c1",
"kind": "class",
"childItems": [
{
"text": "a",
"kind": "property"
},
{
"text": "'line1 line2'",
"kind": "property"
}
]
},
{
"text": "f",
"kind": "function"
},
{
"text": "f(`line1line2line3`) callback",
"kind": "function"
},
{
"text": "f(`unterminated backtick 1unterminated backtick 2unterminated backtick 3) callback",
"kind": "function"
}
]
});
verify.navigationBar([
{
"text": "<global>",
"kind": "script",
"childItems": [
{
"text": "c1",
"kind": "class"
},
{
"text": "f",
"kind": "function"
},
{
"text": "f(`line1line2line3`) callback",
"kind": "function"
},
{
"text": "f(`unterminated backtick 1unterminated backtick 2unterminated backtick 3) callback",
"kind": "function"
}
]
},
{
"text": "c1",
"kind": "class",
"childItems": [
{
"text": "a",
"kind": "property"
},
{
"text": "'line1 line2'",
"kind": "property"
}
],
"indent": 1
},
{
"text": "f",
"kind": "function",
"indent": 1
},
{
"text": "f(`line1line2line3`) callback",
"kind": "function",
"indent": 1
},
{
"text": "f(`unterminated backtick 1unterminated backtick 2unterminated backtick 3) callback",
"kind": "function",
"indent": 1
}
]);

View file

@ -0,0 +1,40 @@
//// declare module 'MoreThanOneHundredAndFiftyCharacters\
//// MoreThanOneHundredAndFiftyCharacters\
//// MoreThanOneHundredAndFiftyCharacters\
//// MoreThanOneHundredAndFiftyCharacters\
//// MoreThanOneHundredAndFiftyCharacters\
//// MoreThanOneHundredAndFiftyCharacters\
//// MoreThanOneHundredAndFiftyCharacters' { }
verify.navigationTree({
"text": "<global>",
"kind": "script",
"childItems": [
{
"text": "'MoreThanOneHundredAndFiftyCharactersMoreThanOneHundredAndFiftyCharactersMoreThanOneHundredAndFiftyCharactersMoreThanOneHundredAndFiftyCharacter...",
"kind": "module",
"kindModifiers": "declare"
}
]
});
verify.navigationBar([
{
"text": "<global>",
"kind": "script",
"childItems": [
{
"text": "'MoreThanOneHundredAndFiftyCharactersMoreThanOneHundredAndFiftyCharactersMoreThanOneHundredAndFiftyCharactersMoreThanOneHundredAndFiftyCharacter...",
"kind": "module",
"kindModifiers": "declare"
}
]
},
{
"text": "'MoreThanOneHundredAndFiftyCharactersMoreThanOneHundredAndFiftyCharactersMoreThanOneHundredAndFiftyCharactersMoreThanOneHundredAndFiftyCharacter...",
"kind": "module",
"kindModifiers": "declare",
"indent": 1
}
]);

View file

@ -0,0 +1,7 @@
/// <reference path="fourslash.ts"/>
//// /// <summary>Text</summary>
var c = classification;
verify.syntacticClassificationsAre(
c.comment("/// <summary>Text</summary>"));

View file

@ -0,0 +1,7 @@
/// <reference path="fourslash.ts"/>
//// /// <reference>Text</reference>
var c = classification;
verify.syntacticClassificationsAre(
c.comment("/// <reference>Text</reference>"));

View file

@ -4,5 +4,4 @@
var c = classification;
verify.syntacticClassificationsAre(
c.comment("/// "),
c.punctuation("<"));
c.comment("/// <")); // Don't classify until we recognize the element name