Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -3058,6 +3058,9 @@ func IsJSDocSingleCommentNodeList(nodeList *NodeList) bool {
return false
}
parent := nodeList.Nodes[0].Parent
if parent == nil {
return false
}
return IsJSDocSingleCommentNode(parent) && nodeList == parent.CommentList()
}

Expand Down
32 changes: 32 additions & 0 deletions internal/checker/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,35 @@ func (c *Checker) GetIndexInfosOfType(t *Type) []*IndexInfo {
func (c *Checker) IsContextSensitive(node *ast.Node) bool {
return c.isContextSensitive(node)
}

func (c *Checker) FillMissingTypeArguments(typeArguments []*Type, typeParameters []*Type, minTypeArgumentCount int, isJavaScriptImplicitAny bool) []*Type {
return c.fillMissingTypeArguments(typeArguments, typeParameters, minTypeArgumentCount, isJavaScriptImplicitAny)
}

func (c *Checker) GetMinTypeArgumentCount(typeParameters []*Type) int {
return c.getMinTypeArgumentCount(typeParameters)
}

func (c *Checker) GetWidenedLiteralType(t *Type) *Type {
return c.getWidenedLiteralType(t)
}

func (c *Checker) IsTypeAssignableTo(source *Type, target *Type) bool {
return c.isTypeAssignableTo(source, target)
}

func (c *Checker) GetUnionTypeEx(types []*Type, unionReduction UnionReduction) *Type {
return c.getUnionTypeEx(types, unionReduction, nil, nil)
}

func (c *Checker) RequiresAddingImplicitUndefined(node *ast.Node) bool {
enclosingDeclaration := ast.FindAncestor(node, ast.IsDeclaration)
if enclosingDeclaration == nil {
enclosingDeclaration = ast.GetSourceFileOfNode(node).AsNode()
}
symbol := node.Symbol()
if symbol == nil {
return false
}
return c.GetEmitResolver().RequiresAddingImplicitUndefined(node, symbol, enclosingDeclaration)
}
5 changes: 5 additions & 0 deletions internal/checker/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,11 @@ func (c *Checker) TypeParameterToStringEx(t *Type, enclosingDeclaration *ast.Nod
return p.Emit(typeParamNode, sourceFile)
}

func (c *Checker) TypeToTypeNodeEx(t *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *ast.TypeNode {
nodeBuilder := c.getNodeBuilderEx(idToSymbol)
return nodeBuilder.TypeToTypeNode(t, enclosingDeclaration, flags, internalFlags, nil)
}

func (c *Checker) TypePredicateToTypePredicateNode(t *TypePredicate, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *ast.TypePredicateNodeNode {
nodeBuilder := c.getNodeBuilderEx(idToSymbol)
return nodeBuilder.TypePredicateToTypePredicateNode(t, enclosingDeclaration, flags, nodebuilder.InternalFlagsNone, nil)
Expand Down
125 changes: 117 additions & 8 deletions internal/fourslash/_scripts/convertFourslash.mts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const IMPORT_UTIL = `. "github.com/microsoft/typescript-go/internal/fourslash/te
// Tests for code fixes not in this set will be skipped during conversion.
const allowedCodeFixIds = new Set([
"fixMissingImport",
"fixMissingTypeAnnotationOnExports",
]);

// File name prefixes for code fix tests that are allowed even without a fixId.
Expand All @@ -42,6 +43,15 @@ const allowedCodeFixDescriptionPrefixes = [
"Add import from ",
"Update import from ",
"Change 'import' to 'import type'",
"Add annotation of type",
"Add return type",
"Add satisfies and an inline type assertion",
"Annotate types of properties expando function",
"Extract default export to variable",
"Extract base class to variable",
"Extract binding expressions to variable",
"Extract to variable and replace with",
"Mark array literal as const",
];

function getManualTests(): Set<string> {
Expand Down Expand Up @@ -167,7 +177,8 @@ function validateCodeFixCommands(commands: Cmd[]): void {
return allowedCodeFixDescriptionPrefixes.some(p => c.description.startsWith(p));
}
if (c.kind === "verifyCodeFixAvailable") {
return c.descriptions.length > 0 && c.descriptions.every(d => allowedCodeFixDescriptionPrefixes.some(p => d.startsWith(p)));
// Empty descriptions means "assert no fixes available", which is always allowed.
return c.descriptions.length === 0 || c.descriptions.every(d => allowedCodeFixDescriptionPrefixes.some(p => d.startsWith(p)));
}
return true;
});
Expand Down Expand Up @@ -1765,17 +1776,27 @@ function parseCodeFixArgs(args: readonly ts.Expression[]): [VerifyCodeFixCmd] {
throw new Error(`Expected object literal in verify.codeFix, got ${args[0].getText()}`);
}

const sourceFile = args[0].getSourceFile();
let description = "";
let newFileContent = "";
let index = 0;
let applyChanges = false;

for (const prop of obj.properties) {
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;
switch (prop.name.text) {
const name = getPropertyName(prop);
if (!name) continue;
if (ts.isShorthandPropertyAssignment(prop)) {
if (name === "description") {
const resolved = resolveDescriptionExpression(prop.name, sourceFile);
if (resolved) description = resolved;
}
continue;
}
if (!ts.isPropertyAssignment(prop)) continue;
switch (name) {
case "description": {
const str = getStringLiteralLike(prop.initializer);
if (str) description = str.text;
const resolved = resolveDescriptionExpression(prop.initializer, sourceFile);
if (resolved) description = resolved;
break;
}
case "newFileContent": {
Expand Down Expand Up @@ -1808,17 +1829,28 @@ function parseCodeFixArgs(args: readonly ts.Expression[]): [VerifyCodeFixCmd] {

function parseCodeFixAvailableArgs(args: readonly ts.Expression[]): [VerifyCodeFixAvailableCmd] {
const descriptions: string[] = [];
let expectNone = false;

if (args.length === 1) {
const sourceFile = args[0].getSourceFile();
const arrayArg = getArrayLiteralExpression(args[0]);
if (arrayArg) {
if (arrayArg.elements.length === 0) {
expectNone = true;
}
for (const elem of arrayArg.elements) {
const obj = getObjectLiteralExpression(elem);
if (obj) {
for (const prop of obj.properties) {
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === "description") {
const str = getStringLiteralLike(prop.initializer);
if (str) descriptions.push(str.text);
if (getPropertyName(prop) === "description") {
let resolved: string | undefined;
if (ts.isPropertyAssignment(prop)) {
resolved = resolveDescriptionExpression(prop.initializer, sourceFile);
}
else if (ts.isShorthandPropertyAssignment(prop)) {
resolved = resolveDescriptionExpression(prop.name, sourceFile);
}
if (resolved) descriptions.push(resolved);
}
}
}
Expand All @@ -1829,6 +1861,7 @@ function parseCodeFixAvailableArgs(args: readonly ts.Expression[]): [VerifyCodeF
return [{
kind: "verifyCodeFixAvailable",
descriptions,
expectNone,
}];
}

Expand Down Expand Up @@ -3510,6 +3543,7 @@ interface VerifyCodeFixCmd {
interface VerifyCodeFixAvailableCmd {
kind: "verifyCodeFixAvailable";
descriptions: string[];
expectNone: boolean;
}

interface VerifyCodeFixAllCmd {
Expand Down Expand Up @@ -3933,6 +3967,9 @@ function generateCmd(cmd: Cmd, imports: Set<string>): string {
}
})`;
case "verifyCodeFixAvailable":
if (cmd.expectNone) {
return `f.VerifyCodeFixAvailable(t, []string{})`;
}
if (cmd.descriptions.length === 0) {
return `f.VerifyCodeFixAvailable(t, nil)`;
}
Expand Down Expand Up @@ -4035,6 +4072,78 @@ function getStringLiteralLike(node: ts.Node): ts.StringLiteralLike | undefined {
return getNodeOfKind(node, ts.isStringLiteralLike);
}

// Build a map from diagnostic property names (e.g. "Extract_base_class_to_variable")
// to their message text, by loading diagnosticMessages.json and applying the same
// key-generation algorithm used by TypeScript's processDiagnosticMessages script.
const diagnosticMessagesByPropName: Map<string, string> = (() => {
const messagesPath = path.resolve(import.meta.dirname, "../", "../", "../", "_submodules", "TypeScript", "src", "compiler", "diagnosticMessages.json");
const raw = JSON.parse(fs.readFileSync(messagesPath, "utf-8"));
Comment thread
jakebailey marked this conversation as resolved.
const map = new Map<string, string>();
for (const messageText of Object.keys(raw)) {
const propName = messageText.split("").map((ch: string) => {
if (ch === "*") return "_Asterisk";
if (ch === "/") return "_Slash";
if (ch === ":") return "_Colon";
return /\w/.test(ch) ? ch : "_";
}).join("")
.replace(/_+/g, "_")
.replace(/^_(\D)/, "$1")
.replace(/_$/, "");
map.set(propName, messageText);
}
return map;
})();

// Resolve a description value from various expression forms:
// - String literal: "Add return type 'void'"
// - ts.Diagnostics.X.message property access
// - Variable identifier referencing a const string in the same file
function resolveDescriptionExpression(expr: ts.Expression, sourceFile: ts.SourceFile): string | undefined {
// String literal
const str = getStringLiteralLike(expr);
if (str) return str.text;

// ts.Diagnostics.Foo_bar.message
if (ts.isPropertyAccessExpression(expr) && expr.name.text === "message") {
const inner = expr.expression;
if (
ts.isPropertyAccessExpression(inner) && ts.isPropertyAccessExpression(inner.expression)
&& ts.isIdentifier(inner.expression.name) && inner.expression.name.text === "Diagnostics"
&& ts.isIdentifier(inner.name)
) {
const diagKey = inner.name.text;
const message = diagnosticMessagesByPropName.get(diagKey);
if (message) return message;
}
}

// Variable reference: look for a const string declaration in the same file
if (ts.isIdentifier(expr)) {
const varName = expr.text;
for (const stmt of sourceFile.statements) {
if (ts.isVariableStatement(stmt)) {
for (const decl of stmt.declarationList.declarations) {
if (ts.isIdentifier(decl.name) && decl.name.text === varName && decl.initializer) {
const initStr = getStringLiteralLike(decl.initializer);
if (initStr) return initStr.text;
}
}
}
}
}

return undefined;
}

// Get the name of a property in an object literal, whether it's an identifier or string literal.
function getPropertyName(prop: ts.ObjectLiteralElementLike): string | undefined {
if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) {
if (ts.isIdentifier(prop.name)) return prop.name.text;
if (ts.isStringLiteral(prop.name)) return prop.name.text;
}
return undefined;
}

function getNumericLiteral(node: ts.Node): ts.NumericLiteral | undefined {
return getNodeOfKind(node, ts.isNumericLiteral);
}
Expand Down
16 changes: 8 additions & 8 deletions internal/fourslash/_scripts/failingTests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ TestAutoImportVerbatimTypeOnly1
TestCalledUnionsOfDissimilarTyeshaveGoodDisplay
TestCloduleTypeOf1
TestCodeCompletionEscaping
TestCodeFixMissingTypeAnnotationOnExports11
TestCodeFixMissingTypeAnnotationOnExports16
TestCodeFixMissingTypeAnnotationOnExports23_heritage_formatting
TestCodeFixMissingTypeAnnotationOnExports24_heritage_formatting_2
TestCodeFixMissingTypeAnnotationOnExports25_heritage_formatting_3
TestCodeFixMissingTypeAnnotationOnExports28_long_types
TestCodeFixUndeclaredPropertyAccesses
TestCodeFixUnusedIdentifier_suggestion
TestCommentsEnumsFourslash
TestCommentsExternalModulesFourslash
TestCommentsImportDeclaration
Expand Down Expand Up @@ -176,26 +184,18 @@ TestExportEqualTypes
TestFindAllRefs_importType_js1
TestFindAllRefs_importType_js2
TestFindAllRefs_importType_js3
TestFormatDocumentPreserveTrailingWhitespace
TestFormatDotAfterNumber
TestFormatMultipleFunctionArguments
TestFormatNoSpaceAfterTemplateHeadAndMiddle
TestFormatSelectionPreserveTrailingWhitespace
TestFormatSpaceAfterTemplateHeadAndMiddle
TestFormatSpaceBetweenFunctionAndArrayIndex
TestFormattingObjectLiteralOpenCurlyNewline
TestFormattingObjectLiteralOpenCurlyNewlineAssignment
TestFormattingObjectLiteralOpenCurlyNewlineTyping
TestFormattingObjectLiteralOpenCurlySingleLine
TestFormattingOnInvalidCodes
TestFormattingOnObjectLiteral
TestFormatTsxClosingAfterJsxText
TestFormatV8Directive
TestFunduleWithRecursiveReference
TestGenericCombinators3
TestGenericCombinatorWithConstraints1
TestGenericFunctionWithGenericParams1
TestGenericsFormattingMultiline
TestGenericTypeWithMultipleBases1MultiFile
TestGetJavaScriptCompletions10
TestGetJavaScriptCompletions12
Expand Down
Loading
Loading