From 576f952997d84492a30749fa0c2f2a55bf4e1f10 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Sun, 7 Dec 2025 16:02:13 +0800 Subject: [PATCH] Fix flow estree type definition (#18437) --- .../generate-flow-estree-type-definition.js | 71 +++++++++- src/language-js/types/estree.d.ts | 55 +++++--- src/language-js/types/flow-estree.d.ts | 128 +++++++++--------- src/language-js/utilities/index.js | 13 +- 4 files changed, 176 insertions(+), 91 deletions(-) diff --git a/scripts/generate-flow-estree-type-definition.js b/scripts/generate-flow-estree-type-definition.js index e80ba4a26..721e70b9e 100644 --- a/scripts/generate-flow-estree-type-definition.js +++ b/scripts/generate-flow-estree-type-definition.js @@ -1,4 +1,7 @@ import fs from "node:fs/promises"; +import url from "node:url"; +import { codeFrameColumns } from "@babel/code-frame"; +import * as ts from "typescript"; import * as prettier from "../index.js"; const PROJECT_ROOT = new URL("../", import.meta.url); @@ -12,11 +15,7 @@ const FLOW_TYPES_DTS = new URL( ); let text = await fs.readFile(FLOW_TYPES, "utf8"); - -text = text.replaceAll("'use strict';", " "); -text = text.replaceAll(/(?<=\n) {2,}\+/gu, " "); -text = text.replaceAll("interface {", "{"); -text = text.replaceAll(/: interface extends (\w+)/gu, ": $1 & "); +text = toDts(text); const getRelativePath = (url) => new URL(url).href.slice(PROJECT_ROOT.href.length); @@ -36,3 +35,65 @@ text = await prettier.format(text, { }); await fs.writeFile(FLOW_TYPES_DTS, text); + +const program = ts.createProgram([url.fileURLToPath(FLOW_TYPES_DTS)], { + strict: true, +}); +const diagnostics = ts.getPreEmitDiagnostics(program); + +if (diagnostics.length > 0) { + await fs.rm(FLOW_TYPES_DTS); + const [diagnostic] = diagnostics; + const { line, character: column } = ts.getLineAndCharacterOfPosition( + diagnostic.file, + diagnostic.start, + ); + throw new Error( + diagnostic.messageText + + ":\n" + + codeFrameColumns( + text, + { start: { line: line + 1, column } }, + { + message: `TS${diagnostic.code}: ${diagnostic.messageText}`, + }, + ), + ); +} + +function toDts(text) { + // Useless directive + text = text.replaceAll("'use strict';", " "); + + // `{+foo: string}` -> `{foo: string}` + text = text.replaceAll(/(?<=\n)(?[ {2}]+)\+/gu, "$"); + + // `{foo: interface {}}` -> `{foo: {}}` + text = text.replaceAll(": interface {", ": {"); + + // `{foo: interface extends T {}}` -> `{foo: T & {}}` + text = text.replaceAll( + /(?<=: )interface extends (?\w+)(?= \{)/gu, + "$ & ", + ); + + // `$ReadOnlyArray` -> `ReadonlyArray` + text = text.replaceAll( + /\$ReadOnlyArray<\?(\w+)>/gu, + "ReadonlyArray<$1 | null>", + ); + + // `$ReadOnlyArray` -> `ReadonlyArray` + text = text.replaceAll("$ReadOnlyArray<", "ReadonlyArray<"); + + // `$ReadOnly` -> `Readonly` + text = text.replaceAll("$ReadOnly<", "Readonly<"); + + // `{[string]: T}` -> `{[key: string]: T}` + text = text.replaceAll( + /(?<=\n)(?[ {2}]+)\[(?string)\](?=: )/gu, + "$[key: $]", + ); + + return text; +} diff --git a/src/language-js/types/estree.d.ts b/src/language-js/types/estree.d.ts index fcde61ffe..eebe91957 100644 --- a/src/language-js/types/estree.d.ts +++ b/src/language-js/types/estree.d.ts @@ -4,6 +4,21 @@ import type { NGTree } from "angular-estree-parser"; import type * as TSESTree from "./typescript-estree.ts"; import type * as FlowESTree from "./flow-estree.js"; +type PrettierNodeAdditionalProperties = { + extra?: { + parenthesized?: boolean; + raw?: string; + }; + comments?: Comment[]; + prettierIgnore?: boolean; +}; + +type PrettierCommentAdditionalProperties = { + printed?: boolean; + trailing?: boolean; + leading?: boolean; +}; + type FlowAdditionalNode = | { type: "SatisfiesExpression"; @@ -16,32 +31,24 @@ type FlowAdditionalNode = type PrettierNode = { type: "JsExpressionRoot"; node: Babel.Expression }; -export type Node = ( - | PrettierNode - | Babel.Node - | TSESTree.Node - | NGTree.NGNode - | FlowESTree.ESNode - | FlowAdditionalNode -) & { - extra?: { - parenthesized?: boolean; - raw?: string; - }; - comments?: Comment[]; -}; - export type Comment = ( | Babel.Comment | TSESTree.Comment | MeriyahESTree.Comment | { type: "Hashbang"; value: string } | Babel.InterpreterDirective -) & { - printed?: boolean; - trailing?: boolean; - leading?: boolean; -}; +) & + PrettierCommentAdditionalProperties; + +type _Node = + | PrettierNode + | Babel.Node + | TSESTree.Node + | NGTree.NGNode + | FlowESTree.ESNode + | FlowAdditionalNode; + +export type Node = ExtendNode<_Node>; export type NodeMap = CreateNodeMap; export type CommentMap = CreateNodeMap; @@ -49,3 +56,11 @@ export type CommentMap = CreateNodeMap; type CreateNodeMap = { [NodeType in InputNode["type"]]: Extract; }; + +type ExtendNode = InputNode extends _Node + ? { + [Key in keyof InputNode]: ExtendNode; + } & PrettierNodeAdditionalProperties + : InputNode extends readonly (_Node | any)[] + ? ExtendNode[] + : InputNode; diff --git a/src/language-js/types/flow-estree.d.ts b/src/language-js/types/flow-estree.d.ts index 1d629811f..fd9dc4496 100644 --- a/src/language-js/types/flow-estree.d.ts +++ b/src/language-js/types/flow-estree.d.ts @@ -117,16 +117,16 @@ export interface InterpreterDirective extends BaseNode { value: string; } -export type DocblockDirectives = $ReadOnly<{ +export type DocblockDirectives = Readonly<{ // some well-known tags - flow?: $ReadOnlyArray | void; - format?: $ReadOnlyArray | void; - noflow?: $ReadOnlyArray | void; - noformat?: $ReadOnlyArray | void; - [string]: $ReadOnlyArray | void; + flow?: ReadonlyArray | void; + format?: ReadonlyArray | void; + noflow?: ReadonlyArray | void; + noformat?: ReadonlyArray | void; + [key: string]: ReadonlyArray | void; }>; -export type DocblockMetadata = $ReadOnly<{ +export type DocblockMetadata = Readonly<{ directives: DocblockDirectives; comment: BlockComment; }>; @@ -134,9 +134,9 @@ export type DocblockMetadata = $ReadOnly<{ export interface Program extends BaseNode { type: "Program"; sourceType: "script" | "module"; - body: $ReadOnlyArray; - tokens: $ReadOnlyArray; - comments: $ReadOnlyArray; + body: ReadonlyArray; + tokens: ReadonlyArray; + comments: ReadonlyArray; loc: SourceLocation; interpreter: null | InterpreterDirective; docblock: null | DocblockMetadata; @@ -226,7 +226,7 @@ export type DestructuringPattern = | RestElement; interface BaseFunction extends BaseNode { - params: $ReadOnlyArray; + params: ReadonlyArray; async: boolean; predicate: null | InferredPredicate; @@ -305,12 +305,12 @@ export interface EmptyStatement extends BaseNode { export interface BlockStatement extends BaseNode { type: "BlockStatement"; - body: $ReadOnlyArray; + body: ReadonlyArray; } export interface StaticBlock extends BaseNode { type: "StaticBlock"; - body: $ReadOnlyArray; + body: ReadonlyArray; } export interface ExpressionStatement extends BaseNode { @@ -351,7 +351,7 @@ export interface WithStatement extends BaseNode { export interface SwitchStatement extends BaseNode { type: "SwitchStatement"; discriminant: Expression; - cases: $ReadOnlyArray; + cases: ReadonlyArray; } export interface ReturnStatement extends BaseNode { @@ -423,7 +423,7 @@ export interface ComponentDeclaration extends BaseNode { type: "ComponentDeclaration"; body: BlockStatement; id: Identifier; - params: $ReadOnlyArray; + params: ReadonlyArray; rendersType: null | RendersType; typeParameters: null | TypeParameterDeclaration; } @@ -432,7 +432,7 @@ export interface HookDeclaration extends BaseNode { type: "HookDeclaration"; id: Identifier; body: BlockStatement; - params: $ReadOnlyArray; + params: ReadonlyArray; returnType: null | TypeAnnotation; typeParameters: null | TypeParameterDeclaration; } @@ -447,7 +447,7 @@ export interface FunctionDeclaration extends BaseFunction { export interface VariableDeclaration extends BaseNode { type: "VariableDeclaration"; - declarations: $ReadOnlyArray; + declarations: ReadonlyArray; kind: "var" | "let" | "const"; } @@ -498,14 +498,14 @@ export interface ThisExpression extends BaseNode { export interface ArrayExpression extends BaseNode { type: "ArrayExpression"; - elements: $ReadOnlyArray; + elements: ReadonlyArray; // this is not part of the ESTree spec, but hermes emits it trailingComma: boolean; } export interface ObjectExpression extends BaseNode { type: "ObjectExpression"; - properties: $ReadOnlyArray; + properties: ReadonlyArray; } // This is the complete type of a "Property" @@ -602,7 +602,7 @@ export interface FunctionExpression extends BaseFunction { export interface SequenceExpression extends BaseNode { type: "SequenceExpression"; - expressions: $ReadOnlyArray; + expressions: ReadonlyArray; } export interface UnaryExpression extends BaseNode { @@ -660,7 +660,7 @@ export interface ConditionalExpression extends BaseNode { interface BaseCallExpression extends BaseNode { callee: Expression | Super; - arguments: $ReadOnlyArray; + arguments: ReadonlyArray; typeArguments: null | TypeParameterInstantiation; } export interface CallExpression extends BaseCallExpression { @@ -700,7 +700,7 @@ export interface ChainExpression extends BaseNode { export interface SwitchCase extends BaseNode { type: "SwitchCase"; test?: Expression | null; - consequent: $ReadOnlyArray; + consequent: ReadonlyArray; } export interface CatchClause extends BaseNode { @@ -861,8 +861,8 @@ export interface YieldExpression extends BaseNode { export interface TemplateLiteral extends BaseNode { type: "TemplateLiteral"; - quasis: $ReadOnlyArray; - expressions: $ReadOnlyArray; + quasis: ReadonlyArray; + expressions: ReadonlyArray; } export interface TaggedTemplateExpression extends BaseNode { @@ -882,7 +882,7 @@ export interface TemplateElement extends BaseNode { export interface ObjectPattern extends BaseNode { type: "ObjectPattern"; - properties: $ReadOnlyArray; + properties: ReadonlyArray; // if used as a VariableDeclarator.id typeAnnotation: TypeAnnotation | null; } @@ -890,7 +890,7 @@ export interface ObjectPattern extends BaseNode { export interface ArrayPattern extends BaseNode { type: "ArrayPattern"; // an element will be null if the pattern contains a hole: `[a,,b]` - elements: $ReadOnlyArray; + elements: ReadonlyArray; typeAnnotation: TypeAnnotation | null; } @@ -913,8 +913,8 @@ interface BaseClass extends BaseNode { typeParameters: null | TypeParameterDeclaration; superTypeParameters: null | TypeParameterInstantiation; - implements: $ReadOnlyArray; - decorators: $ReadOnlyArray; + implements: ReadonlyArray; + decorators: ReadonlyArray; } export type PropertyName = @@ -933,7 +933,7 @@ export type ClassMemberWithNonComputedName = | MethodDefinitionWithNonComputedName; export interface ClassBody extends BaseNode { type: "ClassBody"; - body: $ReadOnlyArray; + body: ReadonlyArray; parent: AClass; } @@ -1029,11 +1029,11 @@ export type ModuleSpecifier = export interface ImportDeclaration extends BaseNode { type: "ImportDeclaration"; - specifiers: $ReadOnlyArray< + specifiers: ReadonlyArray< ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier >; source: StringLiteral; - assertions: $ReadOnlyArray; + assertions: ReadonlyArray; importKind: "value" | "type" | "typeof"; } @@ -1090,7 +1090,7 @@ export type NamedDeclaration = interface ExportNamedDeclarationBase extends BaseNode { type: "ExportNamedDeclaration"; declaration?: NamedDeclaration | null; - specifiers: $ReadOnlyArray; + specifiers: ReadonlyArray; source?: StringLiteral | null; exportKind: "value" | "type"; } @@ -1098,7 +1098,7 @@ export interface ExportNamedDeclarationWithSpecifiers extends ExportNamedDeclara type: "ExportNamedDeclaration"; declaration: null; source?: StringLiteral | null; - specifiers: $ReadOnlyArray; + specifiers: ReadonlyArray; } export interface ExportNamedDeclarationWithDeclaration extends ExportNamedDeclarationBase { type: "ExportNamedDeclaration"; @@ -1301,7 +1301,7 @@ export interface KeyofTypeAnnotation extends BaseNode { } export interface TupleTypeAnnotation extends BaseNode { type: "TupleTypeAnnotation"; - types: $ReadOnlyArray; + types: ReadonlyArray; inexact: boolean; } export interface TupleTypeSpreadElement extends BaseNode { @@ -1340,11 +1340,11 @@ export interface InterfaceTypeAnnotation extends BaseInterfaceNode { export interface UnionTypeAnnotation extends BaseNode { type: "UnionTypeAnnotation"; - types: $ReadOnlyArray; + types: ReadonlyArray; } export interface IntersectionTypeAnnotation extends BaseNode { type: "IntersectionTypeAnnotation"; - types: $ReadOnlyArray; + types: ReadonlyArray; } export interface ConditionalTypeAnnotation extends BaseNode { @@ -1391,7 +1391,7 @@ export interface TypePredicate extends BaseNode { export interface FunctionTypeAnnotation extends BaseNode { type: "FunctionTypeAnnotation"; - params: $ReadOnlyArray; + params: ReadonlyArray; returnType: TypeAnnotationType; rest: null | FunctionTypeParam; typeParameters: null | TypeParameterDeclaration; @@ -1407,7 +1407,7 @@ export interface FunctionTypeParam extends BaseNode { } export interface HookTypeAnnotation extends BaseNode { type: "HookTypeAnnotation"; - params: $ReadOnlyArray; + params: ReadonlyArray; returnType: TypeAnnotationType; rest: null | FunctionTypeParam; typeParameters: null | TypeParameterDeclaration; @@ -1415,7 +1415,7 @@ export interface HookTypeAnnotation extends BaseNode { export interface ComponentTypeAnnotation extends BaseNode { type: "ComponentTypeAnnotation"; - params: $ReadOnlyArray; + params: ReadonlyArray; rest: null | ComponentTypeParameter; typeParameters: null | TypeParameterDeclaration; rendersType: null | RendersType; @@ -1439,12 +1439,12 @@ export interface ObjectTypeAnnotation extends BaseNode { type: "ObjectTypeAnnotation"; inexact: boolean; exact: boolean; - properties: $ReadOnlyArray< + properties: ReadonlyArray< ObjectTypeProperty | ObjectTypeSpreadProperty | ObjectTypeMappedTypeProperty >; - indexers: $ReadOnlyArray; - callProperties: $ReadOnlyArray; - internalSlots: $ReadOnlyArray; + indexers: ReadonlyArray; + callProperties: ReadonlyArray; + internalSlots: ReadonlyArray; } interface ObjectTypePropertyBase extends BaseNode { type: "ObjectTypeProperty"; @@ -1558,7 +1558,7 @@ export interface AsConstExpression extends BaseNode { interface BaseInterfaceNode extends BaseNode { body: ObjectTypeAnnotation; - extends: $ReadOnlyArray; + extends: ReadonlyArray; } interface BaseInterfaceDeclaration extends BaseInterfaceNode { id: Identifier; @@ -1594,7 +1594,7 @@ export interface Decorator extends BaseNode { export interface TypeParameterDeclaration extends BaseNode { type: "TypeParameterDeclaration"; - params: $ReadOnlyArray; + params: ReadonlyArray; } export interface TypeParameter extends BaseNode { type: "TypeParameter"; @@ -1608,7 +1608,7 @@ export interface TypeParameter extends BaseNode { } export interface TypeParameterInstantiation extends BaseNode { type: "TypeParameterInstantiation"; - params: $ReadOnlyArray; + params: ReadonlyArray; parent: GenericTypeAnnotation | CallExpression | NewExpression; } @@ -1634,7 +1634,7 @@ interface BaseInferrableEnumBody extends BaseEnumBody { export interface EnumNumberBody extends BaseInferrableEnumBody { type: "EnumNumberBody"; // enum number members cannot be defaulted - members: $ReadOnlyArray; + members: ReadonlyArray; explicitType: boolean; parent: EnumDeclaration; @@ -1651,7 +1651,7 @@ export interface EnumNumberMember extends BaseNode { export interface EnumBigIntBody extends BaseInferrableEnumBody { type: "EnumBigIntBody"; // enum bigint members cannot be defaulted - members: $ReadOnlyArray; + members: ReadonlyArray; explicitType: boolean; parent: EnumDeclaration; @@ -1667,7 +1667,7 @@ export interface EnumBigIntMember extends BaseNode { export interface EnumStringBody extends BaseInferrableEnumBody { type: "EnumStringBody"; - members: $ReadOnlyArray; + members: ReadonlyArray; parent: EnumDeclaration; } @@ -1683,7 +1683,7 @@ export interface EnumStringMember extends BaseNode { export interface EnumBooleanBody extends BaseInferrableEnumBody { type: "EnumBooleanBody"; // enum boolean members cannot be defaulted - members: $ReadOnlyArray; + members: ReadonlyArray; parent: EnumDeclaration; } @@ -1699,7 +1699,7 @@ export interface EnumBooleanMember extends BaseNode { export interface EnumSymbolBody extends BaseEnumBody { type: "EnumSymbolBody"; // enum symbol members can only be defaulted - members: $ReadOnlyArray; + members: ReadonlyArray; parent: EnumDeclaration; } @@ -1735,10 +1735,10 @@ export interface DeclareClass extends BaseNode { type: "DeclareClass"; id: Identifier; typeParameters: null | TypeParameterDeclaration; - extends: $ReadOnlyArray; - implements: $ReadOnlyArray; + extends: ReadonlyArray; + implements: ReadonlyArray; body: ObjectTypeAnnotation; - mixins: $ReadOnlyArray; + mixins: ReadonlyArray; } export interface DeclareComponent extends BaseNode { @@ -1815,7 +1815,7 @@ export interface DeclareExportAllDeclaration extends BaseNode { interface DeclareExportDeclarationBase extends BaseNode { type: "DeclareExportDeclaration"; - specifiers: $ReadOnlyArray; + specifiers: ReadonlyArray; source: StringLiteral | null; default: boolean; } @@ -1855,7 +1855,7 @@ export interface DeclareExportDeclarationNamedWithSpecifiers extends DeclareExpo declaration: null; default: false; source: StringLiteral; - specifiers: $ReadOnlyArray; + specifiers: ReadonlyArray; } export type DeclareExportDeclaration = | DeclareExportDefaultDeclaration @@ -1930,7 +1930,7 @@ export interface JSXElement extends BaseNode { type: "JSXElement"; openingElement: JSXOpeningElement; closingElement: JSXClosingElement | null; - children: $ReadOnlyArray; + children: ReadonlyArray; } export interface JSXEmptyExpression extends BaseNode { @@ -1946,7 +1946,7 @@ export interface JSXFragment extends BaseNode { type: "JSXFragment"; openingFragment: JSXOpeningFragment; closingFragment: JSXClosingFragment; - children: $ReadOnlyArray; + children: ReadonlyArray; } export interface JSXIdentifier extends BaseNode { @@ -1970,7 +1970,7 @@ export interface JSXOpeningElement extends BaseNode { type: "JSXOpeningElement"; selfClosing: boolean; name: JSXTagNameExpression; - attributes: $ReadOnlyArray; + attributes: ReadonlyArray; typeArguments?: TypeParameterInstantiation | null; parent: JSXElement; @@ -2007,7 +2007,7 @@ export interface JSXSpreadChild extends BaseNode { export interface MatchExpression extends BaseNode { type: "MatchExpression"; argument: Expression; - cases: $ReadOnlyArray; + cases: ReadonlyArray; } export interface MatchExpressionCase extends BaseNode { type: "MatchExpressionCase"; @@ -2019,7 +2019,7 @@ export interface MatchExpressionCase extends BaseNode { export interface MatchStatement extends BaseNode { type: "MatchStatement"; argument: Expression; - cases: $ReadOnlyArray; + cases: ReadonlyArray; } export interface MatchStatementCase extends BaseNode { type: "MatchStatementCase"; @@ -2046,7 +2046,7 @@ export type MatchPattern = export interface MatchOrPattern extends BaseNode { type: "MatchOrPattern"; - patterns: $ReadOnlyArray; + patterns: ReadonlyArray; } export interface MatchAsPattern extends BaseNode { type: "MatchAsPattern"; @@ -2081,7 +2081,7 @@ export interface MatchBindingPattern extends BaseNode { } export interface MatchObjectPattern extends BaseNode { type: "MatchObjectPattern"; - properties: $ReadOnlyArray; + properties: ReadonlyArray; rest: MatchRestPattern | null; } export interface MatchObjectPatternProperty extends BaseNode { @@ -2092,7 +2092,7 @@ export interface MatchObjectPatternProperty extends BaseNode { } export interface MatchArrayPattern extends BaseNode { type: "MatchArrayPattern"; - elements: $ReadOnlyArray; + elements: ReadonlyArray; rest: MatchRestPattern | null; } export interface MatchRestPattern extends BaseNode { diff --git a/src/language-js/utilities/index.js b/src/language-js/utilities/index.js index cbd8882df..e71f6777e 100644 --- a/src/language-js/utilities/index.js +++ b/src/language-js/utilities/index.js @@ -135,6 +135,10 @@ function isBooleanLiteral(node) { ); } +/** +@param {Node} node +@returns {node is NodeMap["UnaryExpression"]} +*/ function isSignedNumericLiteral(node) { return ( node.type === "UnaryExpression" && @@ -623,8 +627,13 @@ function isSimpleCallArgument(node, depth = 2) { if (isObjectExpression(node)) { return node.properties.every( - (p) => - !p.computed && (p.shorthand || (p.value && isChildSimple(p.value))), + (property) => + // @ts-expect-error -- FIXME + !property.computed && + // @ts-expect-error -- FIXME + (property.shorthand || + // @ts-expect-error -- FIXME + (property.value && isChildSimple(property.value))), ); }