diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index e4908d3612f..1f6a9d3be40 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -787,6 +787,7 @@ namespace FourSlash { } if (options.excludes) { for (const exclude of toArray(options.excludes)) { + assert(typeof exclude === "string"); if (nameToEntries.has(exclude)) { this.raiseError(`Did not expect to get a completion named ${exclude}`); } @@ -812,7 +813,9 @@ namespace FourSlash { } if (kind !== undefined || kindModifiers !== undefined) { - assert.equal(actual.kind, kind); + if (actual.kind !== kind) { + this.raiseError(`Unexpected kind for ${actual.name}: Expected ${kind}, actual ${actual.kind}`); + } if (actual.kindModifiers !== (kindModifiers || "")) { this.raiseError(`Bad kind modifiers for ${actual.name}: Expected ${kindModifiers || ""}, actual ${actual.kindModifiers}`); } @@ -4427,110 +4430,119 @@ namespace FourSlashInterface { } } export namespace Completion { - const res: string[] = []; + const functionEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "function", kindModifiers: "declare" }); + const constEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "const", kindModifiers: "declare" }); + const moduleEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "module", kindModifiers: "declare" }); + const keywordEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "keyword" }); + const methodEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "method", kindModifiers: "declare" }); + const propertyEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "property", kindModifiers: "declare" }); + const interfaceEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "interface", kindModifiers: "declare" }); + const typeEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "type", kindModifiers: "declare" }); + + const res: ExpectedCompletionEntryObject[] = []; for (let i = ts.SyntaxKind.FirstKeyword; i <= ts.SyntaxKind.LastKeyword; i++) { - res.push(ts.Debug.assertDefined(ts.tokenToString(i))); + res.push({ name: ts.Debug.assertDefined(ts.tokenToString(i)), kind: "keyword" }); } - export const keywordsWithUndefined: ReadonlyArray = res; - export const keywords: ReadonlyArray = keywordsWithUndefined.filter(k => k !== "undefined"); + export const keywordsWithUndefined: ReadonlyArray = res; + export const keywords: ReadonlyArray = keywordsWithUndefined.filter(k => k.name !== "undefined"); - export const typeKeywords: ReadonlyArray = - ["false", "null", "true", "void", "any", "boolean", "keyof", "never", "number", "object", "string", "symbol", "undefined", "unique", "unknown"]; + export const typeKeywords: ReadonlyArray = + ["false", "null", "true", "void", "any", "boolean", "keyof", "never", "number", "object", "string", "symbol", "undefined", "unique", "unknown"].map(keywordEntry); - const globalTypeDecls = [ - "Symbol", - "PropertyKey", - "PropertyDescriptor", - "PropertyDescriptorMap", - "Object", - "ObjectConstructor", - "Function", - "FunctionConstructor", - "CallableFunction", - "NewableFunction", - "IArguments", - "String", - "StringConstructor", - "Boolean", - "BooleanConstructor", - "Number", - "NumberConstructor", - "TemplateStringsArray", - "ImportMeta", - "Math", - "Date", - "DateConstructor", - "RegExpMatchArray", - "RegExpExecArray", - "RegExp", - "RegExpConstructor", - "Error", - "ErrorConstructor", - "EvalError", - "EvalErrorConstructor", - "RangeError", - "RangeErrorConstructor", - "ReferenceError", - "ReferenceErrorConstructor", - "SyntaxError", - "SyntaxErrorConstructor", - "TypeError", - "TypeErrorConstructor", - "URIError", - "URIErrorConstructor", - "JSON", - "ReadonlyArray", - "ConcatArray", - "Array", - "ArrayConstructor", - "TypedPropertyDescriptor", - "ClassDecorator", - "PropertyDecorator", - "MethodDecorator", - "ParameterDecorator", - "PromiseConstructorLike", - "PromiseLike", - "Promise", - "ArrayLike", - "Partial", - "Required", - "Readonly", - "Pick", - "Record", - "Exclude", - "Extract", - "NonNullable", - "Parameters", - "ConstructorParameters", - "ReturnType", - "InstanceType", - "ThisType", - "ArrayBuffer", - "ArrayBufferTypes", - "ArrayBufferLike", - "ArrayBufferConstructor", - "ArrayBufferView", - "DataView", - "DataViewConstructor", - "Int8Array", - "Int8ArrayConstructor", - "Uint8Array", - "Uint8ArrayConstructor", - "Uint8ClampedArray", - "Uint8ClampedArrayConstructor", - "Int16Array", - "Int16ArrayConstructor", - "Uint16Array", - "Uint16ArrayConstructor", - "Int32Array", - "Int32ArrayConstructor", - "Uint32Array", - "Uint32ArrayConstructor", - "Float32Array", - "Float32ArrayConstructor", - "Float64Array", - "Float64ArrayConstructor", - "Intl", + const globalTypeDecls: ReadonlyArray = [ + interfaceEntry("Symbol"), + typeEntry("PropertyKey"), + interfaceEntry("PropertyDescriptor"), + interfaceEntry("PropertyDescriptorMap"), + constEntry("Object"), + interfaceEntry("ObjectConstructor"), + constEntry("Function"), + interfaceEntry("FunctionConstructor"), + interfaceEntry("CallableFunction"), + interfaceEntry("NewableFunction"), + interfaceEntry("IArguments"), + constEntry("String"), + interfaceEntry("StringConstructor"), + constEntry("Boolean"), + interfaceEntry("BooleanConstructor"), + constEntry("Number"), + interfaceEntry("NumberConstructor"), + interfaceEntry("TemplateStringsArray"), + interfaceEntry("ImportMeta"), + constEntry("Math"), + constEntry("Date"), + interfaceEntry("DateConstructor"), + interfaceEntry("RegExpMatchArray"), + interfaceEntry("RegExpExecArray"), + constEntry("RegExp"), + interfaceEntry("RegExpConstructor"), + constEntry("Error"), + interfaceEntry("ErrorConstructor"), + constEntry("EvalError"), + interfaceEntry("EvalErrorConstructor"), + constEntry("RangeError"), + interfaceEntry("RangeErrorConstructor"), + constEntry("ReferenceError"), + interfaceEntry("ReferenceErrorConstructor"), + constEntry("SyntaxError"), + interfaceEntry("SyntaxErrorConstructor"), + constEntry("TypeError"), + interfaceEntry("TypeErrorConstructor"), + constEntry("URIError"), + interfaceEntry("URIErrorConstructor"), + constEntry("JSON"), + interfaceEntry("ReadonlyArray"), + interfaceEntry("ConcatArray"), + constEntry("Array"), + interfaceEntry("ArrayConstructor"), + interfaceEntry("TypedPropertyDescriptor"), + typeEntry("ClassDecorator"), + typeEntry("PropertyDecorator"), + typeEntry("MethodDecorator"), + typeEntry("ParameterDecorator"), + typeEntry("PromiseConstructorLike"), + interfaceEntry("PromiseLike"), + interfaceEntry("Promise"), + interfaceEntry("ArrayLike"), + typeEntry("Partial"), + typeEntry("Required"), + typeEntry("Readonly"), + typeEntry("Pick"), + typeEntry("Record"), + typeEntry("Exclude"), + typeEntry("Extract"), + typeEntry("NonNullable"), + typeEntry("Parameters"), + typeEntry("ConstructorParameters"), + typeEntry("ReturnType"), + typeEntry("InstanceType"), + interfaceEntry("ThisType"), + constEntry("ArrayBuffer"), + interfaceEntry("ArrayBufferTypes"), + typeEntry("ArrayBufferLike"), + interfaceEntry("ArrayBufferConstructor"), + interfaceEntry("ArrayBufferView"), + constEntry("DataView"), + interfaceEntry("DataViewConstructor"), + constEntry("Int8Array"), + interfaceEntry("Int8ArrayConstructor"), + constEntry("Uint8Array"), + interfaceEntry("Uint8ArrayConstructor"), + constEntry("Uint8ClampedArray"), + interfaceEntry("Uint8ClampedArrayConstructor"), + constEntry("Int16Array"), + interfaceEntry("Int16ArrayConstructor"), + constEntry("Uint16Array"), + interfaceEntry("Uint16ArrayConstructor"), + constEntry("Int32Array"), + interfaceEntry("Int32ArrayConstructor"), + constEntry("Uint32Array"), + interfaceEntry("Uint32ArrayConstructor"), + constEntry("Float32Array"), + interfaceEntry("Float32ArrayConstructor"), + constEntry("Float64Array"), + interfaceEntry("Float64ArrayConstructor"), + moduleEntry("Intl"), ]; export const globalTypes = globalTypesPlus([]); @@ -4543,54 +4555,54 @@ namespace FourSlashInterface { ]; } - export const classElementKeywords: ReadonlyArray = - ["private", "protected", "public", "static", "abstract", "async", "constructor", "get", "readonly", "set"]; + export const classElementKeywords: ReadonlyArray = + ["private", "protected", "public", "static", "abstract", "async", "constructor", "get", "readonly", "set"].map(keywordEntry); - export const constructorParameterKeywords: ReadonlyArray = - ["private", "protected", "public", "readonly"].map((name): ExpectedCompletionEntry => ({ name, kind: "keyword" })); + export const constructorParameterKeywords: ReadonlyArray = + ["private", "protected", "public", "readonly"].map((name): ExpectedCompletionEntryObject => ({ name, kind: "keyword" })); - export const functionMembers: ReadonlyArray = [ - "apply", - "call", - "bind", - "toString", - "length", - { name: "arguments", text: "(property) Function.arguments: any" }, - "caller" + export const functionMembers: ReadonlyArray = [ + methodEntry("apply"), + methodEntry("call"), + methodEntry("bind"), + methodEntry("toString"), + propertyEntry("length"), + { name: "arguments", kind: "property", kindModifiers: "declare", text: "(property) Function.arguments: any" }, + propertyEntry("caller"), ]; - export const stringMembers: ReadonlyArray = [ - "toString", - "charAt", - "charCodeAt", - "concat", - "indexOf", - "lastIndexOf", - "localeCompare", - "match", - "replace", - "search", - "slice", - "split", - "substring", - "toLowerCase", - "toLocaleLowerCase", - "toUpperCase", - "toLocaleUpperCase", - "trim", - "length", - "substr", - "valueOf", + export const stringMembers: ReadonlyArray = [ + methodEntry("toString"), + methodEntry("charAt"), + methodEntry("charCodeAt"), + methodEntry("concat"), + methodEntry("indexOf"), + methodEntry("lastIndexOf"), + methodEntry("localeCompare"), + methodEntry("match"), + methodEntry("replace"), + methodEntry("search"), + methodEntry("slice"), + methodEntry("split"), + methodEntry("substring"), + methodEntry("toLowerCase"), + methodEntry("toLocaleLowerCase"), + methodEntry("toUpperCase"), + methodEntry("toLocaleUpperCase"), + methodEntry("trim"), + propertyEntry("length"), + methodEntry("substr"), + methodEntry("valueOf"), ]; - export const functionMembersWithPrototype: ReadonlyArray = [ + export const functionMembersWithPrototype: ReadonlyArray = [ ...functionMembers.slice(0, 4), - "prototype", + propertyEntry("prototype"), ...functionMembers.slice(4), ]; // TODO: Shouldn't propose type keywords in statement position - export const statementKeywordsWithTypes: ReadonlyArray = [ + export const statementKeywordsWithTypes: ReadonlyArray = [ "break", "case", "catch", @@ -4665,108 +4677,125 @@ namespace FourSlashInterface { "global", "bigint", "of", + ].map(keywordEntry); + + export const statementKeywords: ReadonlyArray = statementKeywordsWithTypes.filter(k => { + const name = k.name; + switch (name) { + case "false": + case "true": + case "null": + case "void": + return true; + case "declare": + case "module": + return false; + default: + return !ts.contains(typeKeywords, k); + } + }); + + export const globalsVars: ReadonlyArray = [ + functionEntry("eval"), + functionEntry("parseInt"), + functionEntry("parseFloat"), + functionEntry("isNaN"), + functionEntry("isFinite"), + functionEntry("decodeURI"), + functionEntry("decodeURIComponent"), + functionEntry("encodeURI"), + functionEntry("encodeURIComponent"), + functionEntry("escape"), + functionEntry("unescape"), + constEntry("NaN"), + constEntry("Infinity"), + constEntry("Object"), + constEntry("Function"), + constEntry("String"), + constEntry("Boolean"), + constEntry("Number"), + constEntry("Math"), + constEntry("Date"), + constEntry("RegExp"), + constEntry("Error"), + constEntry("EvalError"), + constEntry("RangeError"), + constEntry("ReferenceError"), + constEntry("SyntaxError"), + constEntry("TypeError"), + constEntry("URIError"), + constEntry("JSON"), + constEntry("Array"), + constEntry("ArrayBuffer"), + constEntry("DataView"), + constEntry("Int8Array"), + constEntry("Uint8Array"), + constEntry("Uint8ClampedArray"), + constEntry("Int16Array"), + constEntry("Uint16Array"), + constEntry("Int32Array"), + constEntry("Uint32Array"), + constEntry("Float32Array"), + constEntry("Float64Array"), + moduleEntry("Intl"), ]; - export const statementKeywords: ReadonlyArray = statementKeywordsWithTypes.filter(k => - k === "false" || k === "true" || k === "null" || k === "void" || !ts.contains(typeKeywords, k) && k !== "declare" && k !== "module"); - - export const globalsVars: ReadonlyArray = [ - "eval", - "parseInt", - "parseFloat", - "isNaN", - "isFinite", - "decodeURI", - "decodeURIComponent", - "encodeURI", - "encodeURIComponent", - "escape", - "unescape", - "NaN", - "Infinity", - "Object", - "Function", - "String", - "Boolean", - "Number", - "Math", - "Date", - "RegExp", - "Error", - "EvalError", - "RangeError", - "ReferenceError", - "SyntaxError", - "TypeError", - "URIError", - "JSON", - "Array", - "ArrayBuffer", - "DataView", - "Int8Array", - "Uint8Array", - "Uint8ClampedArray", - "Int16Array", - "Uint16Array", - "Int32Array", - "Uint32Array", - "Float32Array", - "Float64Array", - "Intl", - ]; + const globalKeywordsInsideFunction: ReadonlyArray = [ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "enum", + "export", + "extends", + "false", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "null", + "return", + "super", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "var", + "void", + "while", + "with", + "implements", + "interface", + "let", + "package", + "yield", + "async", + ].map(keywordEntry); // TODO: many of these are inappropriate to always provide export const globalsInsideFunction = (plus: ReadonlyArray): ReadonlyArray => [ - "arguments", + { name: "arguments", kind: "local var" }, ...plus, ...globalsVars, - "undefined", - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "enum", - "export", - "extends", - "false", - "finally", - "for", - "function", - "if", - "import", - "in", - "instanceof", - "new", - "null", - "return", - "super", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "var", - "void", - "while", - "with", - "implements", - "interface", - "let", - "package", - "yield", - "async", + { name: "undefined", kind: "var" }, + ...globalKeywordsInsideFunction, ]; // TODO: many of these are inappropriate to always provide - export const globalKeywords: ReadonlyArray = [ + export const globalKeywords: ReadonlyArray = [ "break", "case", "catch", @@ -4841,9 +4870,9 @@ namespace FourSlashInterface { "global", "bigint", "of", - ]; + ].map(keywordEntry); - export const insideMethodKeywords: ReadonlyArray = [ + export const insideMethodKeywords: ReadonlyArray = [ "break", "case", "catch", @@ -4886,17 +4915,21 @@ namespace FourSlashInterface { "package", "yield", "async", - ]; + ].map(keywordEntry); - export const globalKeywordsPlusUndefined: ReadonlyArray = (() => { - const i = globalKeywords.indexOf("unique"); - return [...globalKeywords.slice(0, i), "undefined", ...globalKeywords.slice(i)]; + export const globalKeywordsPlusUndefined: ReadonlyArray = (() => { + const i = ts.findIndex(globalKeywords, x => x.name === "unique"); + return [...globalKeywords.slice(0, i), keywordEntry("undefined"), ...globalKeywords.slice(i)]; })(); - export const globals: ReadonlyArray = [...globalsVars, "undefined", ...globalKeywords]; + export const globals: ReadonlyArray = [ + ...globalsVars, + { name: "undefined", kind: "var" }, + ...globalKeywords + ]; export function globalsPlus(plus: ReadonlyArray): ReadonlyArray { - return [...globalsVars, ...plus, "undefined", ...globalKeywords]; + return [...globalsVars, ...plus, { name: "undefined", kind: "var" }, ...globalKeywords]; } } @@ -4914,20 +4947,21 @@ namespace FourSlashInterface { newContent: NewFileContent; } - export type ExpectedCompletionEntry = string | { - readonly name: string, - readonly source?: string, - readonly insertText?: string, - readonly replacementSpan?: FourSlash.Range, - readonly hasAction?: boolean, // If not specified, will assert that this is false. + export type ExpectedCompletionEntry = string | ExpectedCompletionEntryObject; + export interface ExpectedCompletionEntryObject { + readonly name: string; + readonly source?: string; + readonly insertText?: string; + readonly replacementSpan?: FourSlash.Range; + readonly hasAction?: boolean; // If not specified, will assert that this is false. readonly isRecommended?: boolean; // If not specified, will assert that this is false. - readonly kind?: string, // If not specified, won't assert about this - readonly kindModifiers?: string, // Must be paired with 'kind' + readonly kind?: string; // If not specified, won't assert about this + readonly kindModifiers?: string; // Must be paired with 'kind' readonly text?: string; readonly documentation?: string; readonly sourceDisplay?: string; readonly tags?: ReadonlyArray; - }; + } export interface VerifyCompletionsOptions { readonly marker?: ArrayOrSingle; diff --git a/tests/cases/fourslash/completionsImport_keywords.ts b/tests/cases/fourslash/completionsImport_keywords.ts index 3ac4e0b320e..6d4edd83b36 100644 --- a/tests/cases/fourslash/completionsImport_keywords.ts +++ b/tests/cases/fourslash/completionsImport_keywords.ts @@ -21,22 +21,23 @@ verify.completions( // no reserved words { marker: "break", - includes: { name: "break", text: "break", kind: "keyword" }, - excludes: { name: "break", source: "/a" }, + exact: completion.globals, preferences, }, // no strict mode reserved words { marker: "implements", - includes: { name: "implements", text: "implements", kind: "keyword" }, - excludes: { name: "implements", source: "/a" }, + exact: completion.globals, preferences, }, // yes contextual keywords { marker: "unique", - includes: { name: "unique", source: "/a", sourceDisplay: "./a", text: "(alias) const unique: 0\nexport unique", hasAction: true }, - excludes: { name: "unique", source: undefined }, + exact: [ + ...completion.globalsVars, "undefined", + { name: "unique", source: "/a", sourceDisplay: "./a", text: "(alias) const unique: 0\nexport unique", hasAction: true }, + ...completion.globalKeywords.filter(e => e.name !== "unique"), + ], preferences, }, ); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index d1979265548..ddf55fb41b5 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -514,7 +514,7 @@ declare namespace FourSlashInterface { readonly isGlobalCompletion?: boolean; readonly exact?: ArrayOrSingle; readonly includes?: ArrayOrSingle; - readonly excludes?: ArrayOrSingle; + readonly excludes?: ArrayOrSingle; readonly preferences?: UserPreferences; readonly triggerCharacter?: string; } @@ -648,23 +648,24 @@ declare var format: FourSlashInterface.format; declare var cancellation: FourSlashInterface.cancellation; declare var classification: typeof FourSlashInterface.classification; declare namespace completion { - export const globals: ReadonlyArray; - export const globalKeywords: ReadonlyArray; - export const insideMethodKeywords: ReadonlyArray; - export const globalKeywordsPlusUndefined: ReadonlyArray; - export const globalsVars: ReadonlyArray; - export function globalsInsideFunction(plus: ReadonlyArray): ReadonlyArray; - export function globalsPlus(plus: ReadonlyArray): ReadonlyArray; - export const keywordsWithUndefined: ReadonlyArray; - export const keywords: ReadonlyArray; - export const typeKeywords: ReadonlyArray; - export const globalTypes: ReadonlyArray; - export function globalTypesPlus(plus: ReadonlyArray): ReadonlyArray; - export const classElementKeywords: ReadonlyArray; - export const constructorParameterKeywords: ReadonlyArray; - export const functionMembers: ReadonlyArray; - export const stringMembers: ReadonlyArray; - export const functionMembersWithPrototype: ReadonlyArray; - export const statementKeywordsWithTypes: ReadonlyArray; - export const statementKeywords: ReadonlyArray; + type Entry = FourSlashInterface.ExpectedCompletionEntryObject; + export const globals: ReadonlyArray; + export const globalKeywords: ReadonlyArray; + export const insideMethodKeywords: ReadonlyArray; + export const globalKeywordsPlusUndefined: ReadonlyArray; + export const globalsVars: ReadonlyArray; + export function globalsInsideFunction(plus: ReadonlyArray): ReadonlyArray; + export function globalsPlus(plus: ReadonlyArray): ReadonlyArray; + export const keywordsWithUndefined: ReadonlyArray; + export const keywords: ReadonlyArray; + export const typeKeywords: ReadonlyArray; + export const globalTypes: ReadonlyArray; + export function globalTypesPlus(plus: ReadonlyArray): ReadonlyArray; + export const classElementKeywords: ReadonlyArray; + export const constructorParameterKeywords: ReadonlyArray; + export const functionMembers: ReadonlyArray; + export const stringMembers: ReadonlyArray; + export const functionMembersWithPrototype: ReadonlyArray; + export const statementKeywordsWithTypes: ReadonlyArray; + export const statementKeywords: ReadonlyArray; } diff --git a/tests/cases/fourslash/server/completions02.ts b/tests/cases/fourslash/server/completions02.ts index 1a493af524b..8e44edd8225 100644 --- a/tests/cases/fourslash/server/completions02.ts +++ b/tests/cases/fourslash/server/completions02.ts @@ -7,9 +7,13 @@ ////} ////Foo./**/ -const entryName = (e: FourSlashInterface.ExpectedCompletionEntry) => typeof e === "string" ? e : e.name; -const sortedFunctionMembers = completion.functionMembersWithPrototype.slice().sort((a, b) => entryName(a).localeCompare(entryName(b))); -const exact: ReadonlyArray = [...sortedFunctionMembers, { name: "x", text: "var Foo.x: number" }]; +const sortedFunctionMembers = completion.functionMembersWithPrototype.slice().sort((a, b) => a.name.localeCompare(b.name)); +const exact: ReadonlyArray = [ + ...sortedFunctionMembers.map(e => + e.name === "arguments" ? { ...e, kind: "property", kindModifiers: "declare" } : + e.name === "prototype" ? { ...e, kindModifiers: undefined } : e), + { name: "x", text: "var Foo.x: number" }, +]; verify.completions({ marker: "", exact }); // Make an edit