mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-19 14:13:38 -04:00
Prepare the files for the codemod to upgrade to Flow 0.299.0 (#8262)
This commit is contained in:
Vendored
+1
@@ -130,6 +130,7 @@
|
||||
// Support for Flowtype (for newIDE):
|
||||
"javascript.validate.enable": false,
|
||||
"flow.useNPMPackagedFlow": true,
|
||||
"flow.pathToFlow": "{workspaceFolder}/newIDE/app/node_modules/.bin/flow",
|
||||
|
||||
// Clang format styling (duplicated in scripts/CMakeClangUtils.txt)
|
||||
"C_Cpp.clang_format_style": "{BasedOnStyle: Google, BinPackParameters: false, BinPackArguments: false}"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,570 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
declare class StyleSheet {
|
||||
disabled: boolean;
|
||||
+href: string;
|
||||
+media: MediaList;
|
||||
+ownerNode: Node;
|
||||
+parentStyleSheet: ?StyleSheet;
|
||||
+title: string;
|
||||
+type: string;
|
||||
}
|
||||
|
||||
declare class StyleSheetList {
|
||||
@@iterator(): Iterator<StyleSheet>;
|
||||
length: number;
|
||||
[index: number]: StyleSheet;
|
||||
}
|
||||
|
||||
declare class MediaList {
|
||||
@@iterator(): Iterator<string>;
|
||||
mediaText: string;
|
||||
length: number;
|
||||
item(index: number): ?string;
|
||||
deleteMedium(oldMedium: string): void;
|
||||
appendMedium(newMedium: string): void;
|
||||
[index: number]: string;
|
||||
}
|
||||
|
||||
declare class CSSStyleSheet extends StyleSheet {
|
||||
+cssRules: CSSRuleList;
|
||||
+ownerRule: ?CSSRule;
|
||||
deleteRule(index: number): void;
|
||||
insertRule(rule: string, index: number): number;
|
||||
}
|
||||
|
||||
declare class CSSGroupingRule extends CSSRule {
|
||||
+cssRules: CSSRuleList;
|
||||
deleteRule(index: number): void;
|
||||
insertRule(rule: string, index: number): number;
|
||||
}
|
||||
|
||||
declare class CSSConditionRule extends CSSGroupingRule {
|
||||
conditionText: string;
|
||||
}
|
||||
|
||||
declare class CSSMediaRule extends CSSConditionRule {
|
||||
+media: MediaList;
|
||||
}
|
||||
|
||||
declare class CSSStyleRule extends CSSRule {
|
||||
selectorText: string;
|
||||
+style: CSSStyleDeclaration;
|
||||
}
|
||||
|
||||
declare class CSSRule {
|
||||
cssText: string;
|
||||
+parentRule: ?CSSRule;
|
||||
+parentStyleSheet: ?CSSStyleSheet;
|
||||
+type: number;
|
||||
static STYLE_RULE: number;
|
||||
static MEDIA_RULE: number;
|
||||
static FONT_FACE_RULE: number;
|
||||
static PAGE_RULE: number;
|
||||
static IMPORT_RULE: number;
|
||||
static CHARSET_RULE: number;
|
||||
static UNKNOWN_RULE: number;
|
||||
static KEYFRAMES_RULE: number;
|
||||
static KEYFRAME_RULE: number;
|
||||
static NAMESPACE_RULE: number;
|
||||
static COUNTER_STYLE_RULE: number;
|
||||
static SUPPORTS_RULE: number;
|
||||
static DOCUMENT_RULE: number;
|
||||
static FONT_FEATURE_VALUES_RULE: number;
|
||||
static VIEWPORT_RULE: number;
|
||||
static REGION_STYLE_RULE: number;
|
||||
}
|
||||
|
||||
declare class CSSKeyframeRule extends CSSRule {
|
||||
keyText: string;
|
||||
+style: CSSStyleDeclaration;
|
||||
}
|
||||
|
||||
declare class CSSKeyframesRule extends CSSRule {
|
||||
name: string;
|
||||
+cssRules: CSSRuleList;
|
||||
appendRule(rule: string): void;
|
||||
deleteRule(select: string): void;
|
||||
findRule(select: string): CSSKeyframeRule | null;
|
||||
}
|
||||
|
||||
declare class CSSRuleList {
|
||||
@@iterator(): Iterator<CSSRule>;
|
||||
length: number;
|
||||
item(index: number): ?CSSRule;
|
||||
[index: number]: CSSRule;
|
||||
}
|
||||
|
||||
declare class CSSStyleDeclaration {
|
||||
@@iterator(): Iterator<string>;
|
||||
/* DOM CSS Properties */
|
||||
alignContent: string;
|
||||
alignItems: string;
|
||||
alignSelf: string;
|
||||
all: string;
|
||||
animation: string;
|
||||
animationDelay: string;
|
||||
animationDirection: string;
|
||||
animationDuration: string;
|
||||
animationFillMode: string;
|
||||
animationIterationCount: string;
|
||||
animationName: string;
|
||||
animationPlayState: string;
|
||||
animationTimingFunction: string;
|
||||
backdropFilter: string;
|
||||
webkitBackdropFilter: string;
|
||||
backfaceVisibility: string;
|
||||
background: string;
|
||||
backgroundAttachment: string;
|
||||
backgroundBlendMode: string;
|
||||
backgroundClip: string;
|
||||
backgroundColor: string;
|
||||
backgroundImage: string;
|
||||
backgroundOrigin: string;
|
||||
backgroundPosition: string;
|
||||
backgroundPositionX: string;
|
||||
backgroundPositionY: string;
|
||||
backgroundRepeat: string;
|
||||
backgroundSize: string;
|
||||
blockSize: string;
|
||||
border: string;
|
||||
borderBlockEnd: string;
|
||||
borderBlockEndColor: string;
|
||||
borderBlockEndStyle: string;
|
||||
borderBlockEndWidth: string;
|
||||
borderBlockStart: string;
|
||||
borderBlockStartColor: string;
|
||||
borderBlockStartStyle: string;
|
||||
borderBlockStartWidth: string;
|
||||
borderBottom: string;
|
||||
borderBottomColor: string;
|
||||
borderBottomLeftRadius: string;
|
||||
borderBottomRightRadius: string;
|
||||
borderBottomStyle: string;
|
||||
borderBottomWidth: string;
|
||||
borderCollapse: string;
|
||||
borderColor: string;
|
||||
borderImage: string;
|
||||
borderImageOutset: string;
|
||||
borderImageRepeat: string;
|
||||
borderImageSlice: string;
|
||||
borderImageSource: string;
|
||||
borderImageWidth: string;
|
||||
borderInlineEnd: string;
|
||||
borderInlineEndColor: string;
|
||||
borderInlineEndStyle: string;
|
||||
borderInlineEndWidth: string;
|
||||
borderInlineStart: string;
|
||||
borderInlineStartColor: string;
|
||||
borderInlineStartStyle: string;
|
||||
borderInlineStartWidth: string;
|
||||
borderLeft: string;
|
||||
borderLeftColor: string;
|
||||
borderLeftStyle: string;
|
||||
borderLeftWidth: string;
|
||||
borderRadius: string;
|
||||
borderRight: string;
|
||||
borderRightColor: string;
|
||||
borderRightStyle: string;
|
||||
borderRightWidth: string;
|
||||
borderSpacing: string;
|
||||
borderStyle: string;
|
||||
borderTop: string;
|
||||
borderTopColor: string;
|
||||
borderTopLeftRadius: string;
|
||||
borderTopRightRadius: string;
|
||||
borderTopStyle: string;
|
||||
borderTopWidth: string;
|
||||
borderWidth: string;
|
||||
bottom: string;
|
||||
boxDecorationBreak: string;
|
||||
boxShadow: string;
|
||||
boxSizing: string;
|
||||
breakAfter: string;
|
||||
breakBefore: string;
|
||||
breakInside: string;
|
||||
captionSide: string;
|
||||
clear: string;
|
||||
clip: string;
|
||||
clipPath: string;
|
||||
color: string;
|
||||
columns: string;
|
||||
columnCount: string;
|
||||
columnFill: string;
|
||||
columnGap: string;
|
||||
columnRule: string;
|
||||
columnRuleColor: string;
|
||||
columnRuleStyle: string;
|
||||
columnRuleWidth: string;
|
||||
columnSpan: string;
|
||||
columnWidth: string;
|
||||
contain: string;
|
||||
content: string;
|
||||
counterIncrement: string;
|
||||
counterReset: string;
|
||||
cursor: string;
|
||||
direction: string;
|
||||
display: string;
|
||||
emptyCells: string;
|
||||
filter: string;
|
||||
flex: string;
|
||||
flexBasis: string;
|
||||
flexDirection: string;
|
||||
flexFlow: string;
|
||||
flexGrow: string;
|
||||
flexShrink: string;
|
||||
flexWrap: string;
|
||||
float: string;
|
||||
font: string;
|
||||
fontFamily: string;
|
||||
fontFeatureSettings: string;
|
||||
fontKerning: string;
|
||||
fontLanguageOverride: string;
|
||||
fontSize: string;
|
||||
fontSizeAdjust: string;
|
||||
fontStretch: string;
|
||||
fontStyle: string;
|
||||
fontSynthesis: string;
|
||||
fontVariant: string;
|
||||
fontVariantAlternates: string;
|
||||
fontVariantCaps: string;
|
||||
fontVariantEastAsian: string;
|
||||
fontVariantLigatures: string;
|
||||
fontVariantNumeric: string;
|
||||
fontVariantPosition: string;
|
||||
fontWeight: string;
|
||||
grad: string;
|
||||
grid: string;
|
||||
gridArea: string;
|
||||
gridAutoColumns: string;
|
||||
gridAutoFlow: string;
|
||||
gridAutoPosition: string;
|
||||
gridAutoRows: string;
|
||||
gridColumn: string;
|
||||
gridColumnStart: string;
|
||||
gridColumnEnd: string;
|
||||
gridRow: string;
|
||||
gridRowStart: string;
|
||||
gridRowEnd: string;
|
||||
gridTemplate: string;
|
||||
gridTemplateAreas: string;
|
||||
gridTemplateRows: string;
|
||||
gridTemplateColumns: string;
|
||||
height: string;
|
||||
hyphens: string;
|
||||
imageRendering: string;
|
||||
imageResolution: string;
|
||||
imageOrientation: string;
|
||||
imeMode: string;
|
||||
inherit: string;
|
||||
initial: string;
|
||||
inlineSize: string;
|
||||
isolation: string;
|
||||
justifyContent: string;
|
||||
left: string;
|
||||
letterSpacing: string;
|
||||
lineBreak: string;
|
||||
lineHeight: string;
|
||||
listStyle: string;
|
||||
listStyleImage: string;
|
||||
listStylePosition: string;
|
||||
listStyleType: string;
|
||||
margin: string;
|
||||
marginBlockEnd: string;
|
||||
marginBlockStart: string;
|
||||
marginBottom: string;
|
||||
marginInlineEnd: string;
|
||||
marginInlineStart: string;
|
||||
marginLeft: string;
|
||||
marginRight: string;
|
||||
marginTop: string;
|
||||
marks: string;
|
||||
mask: string;
|
||||
maskType: string;
|
||||
maxBlockSize: string;
|
||||
maxHeight: string;
|
||||
maxInlineSize: string;
|
||||
maxWidth: string;
|
||||
minBlockSize: string;
|
||||
minHeight: string;
|
||||
minInlineSize: string;
|
||||
minWidth: string;
|
||||
mixBlendMode: string;
|
||||
mozTransform: string;
|
||||
mozTransformOrigin: string;
|
||||
mozTransitionDelay: string;
|
||||
mozTransitionDuration: string;
|
||||
mozTransitionProperty: string;
|
||||
mozTransitionTimingFunction: string;
|
||||
objectFit: string;
|
||||
objectPosition: string;
|
||||
offsetBlockEnd: string;
|
||||
offsetBlockStart: string;
|
||||
offsetInlineEnd: string;
|
||||
offsetInlineStart: string;
|
||||
opacity: string;
|
||||
order: string;
|
||||
orphans: string;
|
||||
outline: string;
|
||||
outlineColor: string;
|
||||
outlineOffset: string;
|
||||
outlineStyle: string;
|
||||
outlineWidth: string;
|
||||
overflow: string;
|
||||
overflowWrap: string;
|
||||
overflowX: string;
|
||||
overflowY: string;
|
||||
padding: string;
|
||||
paddingBlockEnd: string;
|
||||
paddingBlockStart: string;
|
||||
paddingBottom: string;
|
||||
paddingInlineEnd: string;
|
||||
paddingInlineStart: string;
|
||||
paddingLeft: string;
|
||||
paddingRight: string;
|
||||
paddingTop: string;
|
||||
pageBreakAfter: string;
|
||||
pageBreakBefore: string;
|
||||
pageBreakInside: string;
|
||||
perspective: string;
|
||||
perspectiveOrigin: string;
|
||||
pointerEvents: string;
|
||||
position: string;
|
||||
quotes: string;
|
||||
rad: string;
|
||||
resize: string;
|
||||
right: string;
|
||||
rubyAlign: string;
|
||||
rubyMerge: string;
|
||||
rubyPosition: string;
|
||||
scrollBehavior: string;
|
||||
scrollSnapCoordinate: string;
|
||||
scrollSnapDestination: string;
|
||||
scrollSnapPointsX: string;
|
||||
scrollSnapPointsY: string;
|
||||
scrollSnapType: string;
|
||||
shapeImageThreshold: string;
|
||||
shapeMargin: string;
|
||||
shapeOutside: string;
|
||||
tableLayout: string;
|
||||
tabSize: string;
|
||||
textAlign: string;
|
||||
textAlignLast: string;
|
||||
textCombineUpright: string;
|
||||
textDecoration: string;
|
||||
textDecorationColor: string;
|
||||
textDecorationLine: string;
|
||||
textDecorationStyle: string;
|
||||
textIndent: string;
|
||||
textOrientation: string;
|
||||
textOverflow: string;
|
||||
textRendering: string;
|
||||
textShadow: string;
|
||||
textTransform: string;
|
||||
textUnderlinePosition: string;
|
||||
top: string;
|
||||
touchAction: string;
|
||||
transform: string;
|
||||
transformOrigin: string;
|
||||
transformStyle: string;
|
||||
transition: string;
|
||||
transitionDelay: string;
|
||||
transitionDuration: string;
|
||||
transitionProperty: string;
|
||||
transitionTimingFunction: string;
|
||||
turn: string;
|
||||
unicodeBidi: string;
|
||||
unicodeRange: string;
|
||||
userSelect: string;
|
||||
verticalAlign: string;
|
||||
visibility: string;
|
||||
webkitOverflowScrolling: string;
|
||||
webkitTransform: string;
|
||||
webkitTransformOrigin: string;
|
||||
webkitTransitionDelay: string;
|
||||
webkitTransitionDuration: string;
|
||||
webkitTransitionProperty: string;
|
||||
webkitTransitionTimingFunction: string;
|
||||
whiteSpace: string;
|
||||
widows: string;
|
||||
width: string;
|
||||
willChange: string;
|
||||
wordBreak: string;
|
||||
wordSpacing: string;
|
||||
wordWrap: string;
|
||||
writingMode: string;
|
||||
zIndex: string;
|
||||
|
||||
cssFloat: string;
|
||||
cssText: string;
|
||||
getPropertyPriority(property: string): string;
|
||||
getPropertyValue(property:string): string;
|
||||
item(index: number): string;
|
||||
[index: number]: string;
|
||||
length: number;
|
||||
parentRule: CSSRule;
|
||||
removeProperty(property: string): string;
|
||||
setProperty(property: string, value: ?string, priority: ?string): void;
|
||||
setPropertyPriority(property: string, priority: string): void;
|
||||
}
|
||||
|
||||
declare class TransitionEvent extends Event {
|
||||
elapsedTime: number; // readonly
|
||||
pseudoElement: string; // readonly
|
||||
propertyName: string; // readonly
|
||||
}
|
||||
|
||||
type AnimationPlayState = 'idle' | 'running' | 'paused' | 'finished'
|
||||
type AnimationReplaceState = 'active' | 'removed' | 'persisted'
|
||||
type FillMode = 'none' | 'forwards' | 'backwards' | 'both' | 'auto'
|
||||
type PlaybackDirection = 'normal' | 'reverse' | 'alternate' | 'alternate-reverse'
|
||||
type IterationCompositeOperation = 'replace' | 'accumulate'
|
||||
type CompositeOperation = 'replace' | 'add' | 'accumulate'
|
||||
type CompositeOperationOrAuto = CompositeOperation | 'auto'
|
||||
|
||||
declare class AnimationTimeline {
|
||||
+currentTime: number | null;
|
||||
}
|
||||
|
||||
type DocumentTimelineOptions = {
|
||||
originTime?: DOMHighResTimeStamp;
|
||||
...
|
||||
}
|
||||
|
||||
declare class DocumentTimeline extends AnimationTimeline {
|
||||
constructor(options?: DocumentTimelineOptions): void;
|
||||
}
|
||||
|
||||
type EffectTiming = {
|
||||
delay: number;
|
||||
endDelay: number;
|
||||
fill: FillMode;
|
||||
iterationStart: number;
|
||||
iterations: number;
|
||||
duration: number | string;
|
||||
direction: PlaybackDirection;
|
||||
easing: string;
|
||||
...
|
||||
}
|
||||
|
||||
type OptionalEffectTiming = $Rest<EffectTiming, {...}>
|
||||
|
||||
type ComputedEffectTiming = EffectTiming & {
|
||||
endTime: number;
|
||||
activeDuration: number;
|
||||
localTime: number | null;
|
||||
progress: number | null;
|
||||
currentIteration: number | null;
|
||||
...
|
||||
}
|
||||
|
||||
declare class AnimationEffect {
|
||||
getTiming(): EffectTiming;
|
||||
getComputedTiming(): ComputedEffectTiming;
|
||||
updateTiming(timing?: OptionalEffectTiming): void;
|
||||
}
|
||||
|
||||
type Keyframe = {
|
||||
composite?: CompositeOperationOrAuto;
|
||||
easing?: string;
|
||||
offset?: number | null;
|
||||
[property: string]: string | number | null | void;
|
||||
...
|
||||
}
|
||||
|
||||
type ComputedKeyframe = {
|
||||
composite: CompositeOperationOrAuto;
|
||||
computedOffset: number;
|
||||
easing: string;
|
||||
offset: number | null;
|
||||
[property: string]: string | number | null | void;
|
||||
...
|
||||
}
|
||||
|
||||
type PropertyIndexedKeyframes = {
|
||||
composite?: CompositeOperationOrAuto | CompositeOperationOrAuto[];
|
||||
easing?: string | string[];
|
||||
offset?: number | (number | null)[];
|
||||
[property: string]: string | string[] | number | null | (number | null)[] | void;
|
||||
...
|
||||
}
|
||||
|
||||
type KeyframeEffectOptions = $Rest<EffectTiming, {...}> & {
|
||||
iterationComposite?: IterationCompositeOperation;
|
||||
composite?: CompositeOperation;
|
||||
...
|
||||
}
|
||||
|
||||
declare class KeyframeEffect extends AnimationEffect {
|
||||
constructor(
|
||||
target: Element | null,
|
||||
keyframes: Keyframe[] | PropertyIndexedKeyframes | null,
|
||||
options?: number | KeyframeEffectOptions,
|
||||
): void;
|
||||
constructor(source: KeyframeEffect): void;
|
||||
|
||||
target: Element | null;
|
||||
iterationComposite: IterationCompositeOperation;
|
||||
composite: CompositeOperation;
|
||||
getKeyframes(): ComputedKeyframe[];
|
||||
setKeyframes(keyframes: Keyframe[] | PropertyIndexedKeyframes | null): void;
|
||||
}
|
||||
|
||||
declare class Animation extends EventTarget {
|
||||
constructor(effect?: AnimationEffect | null, timeline?: AnimationTimeline | null): void;
|
||||
|
||||
id: string;
|
||||
effect: AnimationEffect | null;
|
||||
timeline: AnimationTimeline | null;
|
||||
startTime: number | null;
|
||||
currentTime: number | null;
|
||||
playbackRate: number;
|
||||
+playState: AnimationPlayState;
|
||||
+replaceState: AnimationReplaceState;
|
||||
+pending: boolean;
|
||||
+ready: Promise<Animation>;
|
||||
+finished: Promise<Animation>;
|
||||
onfinish: ?((ev: AnimationPlaybackEvent) => mixed);
|
||||
oncancel: ?((ev: AnimationPlaybackEvent) => mixed);
|
||||
onremove: ?((ev: AnimationPlaybackEvent) => mixed);
|
||||
cancel(): void;
|
||||
finish(): void;
|
||||
play(): void;
|
||||
pause(): void;
|
||||
updatePlaybackRate(playbackRate: number): void;
|
||||
reverse(): void;
|
||||
persist(): void;
|
||||
commitStyles(): void;
|
||||
}
|
||||
|
||||
type KeyframeAnimationOptions = KeyframeEffectOptions & {
|
||||
id?: string;
|
||||
...
|
||||
}
|
||||
|
||||
type GetAnimationsOptions = {
|
||||
subtree?: boolean;
|
||||
...
|
||||
}
|
||||
|
||||
interface Animatable {
|
||||
animate(keyframes: Keyframe[] | PropertyIndexedKeyframes | null, options?: number | KeyframeAnimationOptions): Animation;
|
||||
getAnimations(options?: GetAnimationsOptions): Animation[];
|
||||
}
|
||||
|
||||
type AnimationPlaybackEvent$Init = Event$Init & {
|
||||
currentTime?: number | null;
|
||||
timelineTime?: number | null;
|
||||
...
|
||||
}
|
||||
|
||||
declare class AnimationPlaybackEvent extends Event {
|
||||
constructor(type: string, animationEventInitDict?: AnimationPlaybackEvent$Init): void;
|
||||
+currentTime: number | null;
|
||||
+timelineTime: number | null;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// Implemented by window & worker
|
||||
declare interface IDBEnvironment {
|
||||
indexedDB: IDBFactory;
|
||||
}
|
||||
|
||||
type IDBDirection = 'next' | 'nextunique' | 'prev' | 'prevunique';
|
||||
|
||||
// Implemented by window.indexedDB & worker.indexedDB
|
||||
declare interface IDBFactory {
|
||||
open(name: string, version?: number): IDBOpenDBRequest;
|
||||
deleteDatabase(name: string): IDBOpenDBRequest;
|
||||
cmp(a: any, b: any): -1|0|1;
|
||||
}
|
||||
|
||||
declare interface IDBRequest extends EventTarget {
|
||||
result: IDBObjectStore;
|
||||
error: Error;
|
||||
source: ?(IDBIndex | IDBObjectStore | IDBCursor);
|
||||
transaction: IDBTransaction;
|
||||
readyState: 'pending'|'done';
|
||||
onerror: (err: any) => mixed;
|
||||
onsuccess: (e: any) => mixed;
|
||||
}
|
||||
|
||||
declare interface IDBOpenDBRequest extends IDBRequest {
|
||||
onblocked: (e: any) => mixed;
|
||||
onupgradeneeded: (e: any) => mixed;
|
||||
}
|
||||
|
||||
declare interface IDBDatabase extends EventTarget {
|
||||
close(): void;
|
||||
createObjectStore(name: string, options?: {
|
||||
keyPath?: ?(string|string[]),
|
||||
autoIncrement?: bool,
|
||||
...
|
||||
}): IDBObjectStore;
|
||||
deleteObjectStore(name: string): void;
|
||||
transaction(storeNames: string|string[], mode?: 'readonly'|'readwrite'|'versionchange'): IDBTransaction;
|
||||
name: string;
|
||||
version: number;
|
||||
objectStoreNames: string[];
|
||||
onabort: (e: any) => mixed;
|
||||
onclose: (e: any) => mixed;
|
||||
onerror: (e: any) => mixed;
|
||||
onversionchange: (e: any) => mixed;
|
||||
}
|
||||
|
||||
declare interface IDBTransaction extends EventTarget {
|
||||
abort(): void;
|
||||
db: IDBDatabase;
|
||||
error: Error;
|
||||
mode: 'readonly'|'readwrite'|'versionchange';
|
||||
name: string;
|
||||
objectStore(name: string): IDBObjectStore;
|
||||
onabort: (e: any) => mixed;
|
||||
oncomplete: (e: any) => mixed;
|
||||
onerror: (e: any) => mixed;
|
||||
}
|
||||
|
||||
declare interface IDBObjectStore {
|
||||
add(value: any, key?: any): IDBRequest;
|
||||
autoIncrement: bool;
|
||||
clear(): IDBRequest;
|
||||
createIndex(indexName: string, keyPath: string|string[], optionalParameter?: {
|
||||
unique?: bool,
|
||||
multiEntry?: bool,
|
||||
...
|
||||
}): IDBIndex;
|
||||
count(keyRange?: any|IDBKeyRange): IDBRequest;
|
||||
delete(key: any): IDBRequest;
|
||||
deleteIndex(indexName: string): void;
|
||||
get(key: any): IDBRequest;
|
||||
index(indexName: string): IDBIndex;
|
||||
indexNames: string[];
|
||||
name: string;
|
||||
keyPath: any;
|
||||
openCursor(range?: any|IDBKeyRange, direction?: IDBDirection): IDBRequest;
|
||||
openKeyCursor(range?: any|IDBKeyRange, direction?: IDBDirection): IDBRequest;
|
||||
put(value: any, key?: any): IDBRequest;
|
||||
transaction : IDBTransaction;
|
||||
}
|
||||
|
||||
declare interface IDBIndex extends EventTarget {
|
||||
count(key?: any|IDBKeyRange): IDBRequest;
|
||||
get(key: any|IDBKeyRange): IDBRequest;
|
||||
getKey(key: any|IDBKeyRange): IDBRequest;
|
||||
openCursor(range?: any|IDBKeyRange, direction?: IDBDirection): IDBRequest;
|
||||
openKeyCursor(range?: any|IDBKeyRange, direction?: IDBDirection): IDBRequest;
|
||||
name: string;
|
||||
objectStore: IDBObjectStore;
|
||||
keyPath: any;
|
||||
multiEntry: bool;
|
||||
unique: bool;
|
||||
}
|
||||
|
||||
declare interface IDBKeyRange {
|
||||
bound(lower: any, upper: any, lowerOpen?: bool, upperOpen?: bool): IDBKeyRange;
|
||||
only(value: any): IDBKeyRange;
|
||||
lowerBound(bound: any, open?: bool): IDBKeyRange;
|
||||
upperBound(bound: any, open?: bool): IDBKeyRange;
|
||||
lower: any;
|
||||
upper: any;
|
||||
lowerOpen: bool;
|
||||
upperOpen: bool;
|
||||
}
|
||||
|
||||
declare interface IDBCursor {
|
||||
advance(count: number): void;
|
||||
continue(key?: any): void;
|
||||
delete(): IDBRequest;
|
||||
update(newValue: any): IDBRequest;
|
||||
source: IDBObjectStore|IDBIndex;
|
||||
direction: IDBDirection;
|
||||
key: any;
|
||||
primaryKey: any;
|
||||
}
|
||||
|
||||
declare interface IDBCursorWithValue extends IDBCursor {
|
||||
value: any;
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
declare var Intl: {
|
||||
Collator: Class<Intl$Collator>,
|
||||
DateTimeFormat: Class<Intl$DateTimeFormat>,
|
||||
NumberFormat: Class<Intl$NumberFormat>,
|
||||
PluralRules: ?Class<Intl$PluralRules>,
|
||||
getCanonicalLocales?: (locales?: Intl$Locales) => Intl$Locale[],
|
||||
...
|
||||
}
|
||||
|
||||
type Intl$Locale = string
|
||||
type Intl$Locales = Intl$Locale | Intl$Locale[]
|
||||
|
||||
declare class Intl$Collator {
|
||||
constructor (
|
||||
locales?: Intl$Locales,
|
||||
options?: Intl$CollatorOptions
|
||||
): Intl$Collator;
|
||||
|
||||
static (
|
||||
locales?: Intl$Locales,
|
||||
options?: Intl$CollatorOptions
|
||||
): Intl$Collator;
|
||||
|
||||
compare (string, string): number;
|
||||
|
||||
resolvedOptions (): {
|
||||
locale: Intl$Locale,
|
||||
usage: 'sort' | 'search',
|
||||
sensitivity: 'base' | 'accent' | 'case' | 'variant',
|
||||
ignorePunctuation: boolean,
|
||||
collation: string,
|
||||
numeric: boolean,
|
||||
caseFirst?: 'upper' | 'lower' | 'false',
|
||||
...
|
||||
};
|
||||
|
||||
static supportedLocalesOf (locales?: Intl$Locales): Intl$Locale[];
|
||||
}
|
||||
|
||||
declare type Intl$CollatorOptions = {
|
||||
localeMatcher?: 'lookup' | 'best fit',
|
||||
usage?: 'sort' | 'search',
|
||||
sensitivity?: 'base' | 'accent' | 'case' | 'variant',
|
||||
ignorePunctuation?: boolean,
|
||||
numeric?: boolean,
|
||||
caseFirst?: 'upper' | 'lower' | 'false',
|
||||
...
|
||||
}
|
||||
|
||||
type FormatToPartsType = | 'day' | 'dayPeriod' | 'era' | 'hour' | 'literal'
|
||||
| 'minute' | 'month' | 'second' | 'timeZoneName' | 'weekday' | 'year';
|
||||
|
||||
declare class Intl$DateTimeFormat {
|
||||
constructor (
|
||||
locales?: Intl$Locales,
|
||||
options?: Intl$DateTimeFormatOptions
|
||||
): Intl$DateTimeFormat;
|
||||
|
||||
static (
|
||||
locales?: Intl$Locales,
|
||||
options?: Intl$DateTimeFormatOptions
|
||||
): Intl$DateTimeFormat;
|
||||
|
||||
format (value?: Date | number): string;
|
||||
|
||||
formatToParts (value?: Date | number): Array<{
|
||||
type: FormatToPartsType,
|
||||
value: string,
|
||||
...
|
||||
}>;
|
||||
|
||||
resolvedOptions (): {
|
||||
locale: Intl$Locale,
|
||||
calendar: string,
|
||||
numberingSystem: string,
|
||||
timeZone?: string,
|
||||
hour12: boolean,
|
||||
weekday?: 'narrow' | 'short' | 'long',
|
||||
era?: 'narrow' | 'short' | 'long',
|
||||
year?: 'numeric' | '2-digit',
|
||||
month?: 'numeric' | '2-digit' | 'narrow' | 'short' | 'long',
|
||||
day?: 'numeric' | '2-digit',
|
||||
hour?: 'numeric' | '2-digit',
|
||||
minute?: 'numeric' | '2-digit',
|
||||
second?: 'numeric' | '2-digit',
|
||||
timeZoneName?: 'short' | 'long',
|
||||
...
|
||||
};
|
||||
|
||||
static supportedLocalesOf (locales?: Intl$Locales): Intl$Locale[];
|
||||
}
|
||||
|
||||
declare type Intl$DateTimeFormatOptions = {
|
||||
localeMatcher?: 'lookup' | 'best fit',
|
||||
timeZone?: string,
|
||||
hour12?: boolean,
|
||||
formatMatcher?: 'basic' | 'best fit',
|
||||
weekday?: 'narrow' | 'short' | 'long',
|
||||
era?: 'narrow' | 'short' | 'long',
|
||||
year?: 'numeric' | '2-digit',
|
||||
month?: 'numeric' | '2-digit' | 'narrow' | 'short' | 'long',
|
||||
day?: 'numeric' | '2-digit',
|
||||
hour?: 'numeric' | '2-digit',
|
||||
minute?: 'numeric' | '2-digit',
|
||||
second?: 'numeric' | '2-digit',
|
||||
timeZoneName?: 'short' | 'long',
|
||||
...
|
||||
}
|
||||
|
||||
declare class Intl$NumberFormat {
|
||||
constructor (
|
||||
locales?: Intl$Locales,
|
||||
options?: Intl$NumberFormatOptions
|
||||
): Intl$NumberFormat;
|
||||
|
||||
static (
|
||||
locales?: Intl$Locales,
|
||||
options?: Intl$NumberFormatOptions
|
||||
): Intl$NumberFormat;
|
||||
|
||||
format (number): string;
|
||||
|
||||
resolvedOptions (): {
|
||||
locale: Intl$Locale,
|
||||
numberingSystem: string,
|
||||
style: 'decimal' | 'currency' | 'percent',
|
||||
currency?: string,
|
||||
currencyDisplay?: 'symbol' | 'code' | 'name',
|
||||
useGrouping: boolean,
|
||||
minimumIntegerDigits?: number,
|
||||
minimumFractionDigits?: number,
|
||||
maximumFractionDigits?: number,
|
||||
minimumSignificantDigits?: number,
|
||||
maximumSignificantDigits?: number,
|
||||
...
|
||||
};
|
||||
|
||||
static supportedLocalesOf (locales?: Intl$Locales): Intl$Locale[];
|
||||
}
|
||||
|
||||
declare type Intl$NumberFormatOptions = {
|
||||
localeMatcher?: 'lookup' | 'best fit',
|
||||
style?: 'decimal' | 'currency' | 'percent',
|
||||
currency?: string,
|
||||
currencyDisplay?: 'symbol' | 'code' | 'name',
|
||||
useGrouping?: boolean,
|
||||
minimumIntegerDigits?: number,
|
||||
minimumFractionDigits?: number,
|
||||
maximumFractionDigits?: number,
|
||||
minimumSignificantDigits?: number,
|
||||
maximumSignificantDigits?: number,
|
||||
...
|
||||
}
|
||||
|
||||
declare class Intl$PluralRules {
|
||||
constructor (
|
||||
locales?: Intl$Locales,
|
||||
options?: Intl$PluralRulesOptions
|
||||
): Intl$PluralRules;
|
||||
|
||||
select (number): Intl$PluralRule;
|
||||
|
||||
resolvedOptions (): {
|
||||
locale: Intl$Locale,
|
||||
type: 'cardinal' | 'ordinal',
|
||||
minimumIntegerDigits?: number,
|
||||
minimumFractionDigits?: number,
|
||||
maximumFractionDigits?: number,
|
||||
minimumSignificantDigits?: number,
|
||||
maximumSignificantDigits?: number,
|
||||
pluralCategories: Intl$PluralRule[],
|
||||
...
|
||||
};
|
||||
|
||||
static supportedLocalesOf (locales?: Intl$Locales): Intl$Locale[];
|
||||
}
|
||||
|
||||
type Intl$PluralRule = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other'
|
||||
|
||||
declare type Intl$PluralRulesOptions = {
|
||||
localeMatcher?: 'lookup' | 'best fit',
|
||||
type?: 'cardinal' | 'ordinal',
|
||||
minimumIntegerDigits?: number,
|
||||
minimumFractionDigits?: number,
|
||||
maximumFractionDigits?: number,
|
||||
minimumSignificantDigits?: number,
|
||||
maximumSignificantDigits?: number,
|
||||
...
|
||||
}
|
||||
Vendored
+692
@@ -0,0 +1,692 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @nolint
|
||||
* @format
|
||||
*/
|
||||
|
||||
// The react-dom, react-dom/server and react-dom/test-utils modules were moved to flow-typed.
|
||||
|
||||
declare class SyntheticEvent<+T: EventTarget = EventTarget, +E: Event = Event> {
|
||||
bubbles: boolean;
|
||||
cancelable: boolean;
|
||||
+currentTarget: T;
|
||||
defaultPrevented: boolean;
|
||||
eventPhase: number;
|
||||
isDefaultPrevented(): boolean;
|
||||
isPropagationStopped(): boolean;
|
||||
isTrusted: boolean;
|
||||
nativeEvent: E;
|
||||
preventDefault(): void;
|
||||
stopPropagation(): void;
|
||||
// This should not be `T`. Use `currentTarget` instead. See:
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11508#issuecomment-256045682
|
||||
+target: EventTarget;
|
||||
timeStamp: number;
|
||||
type: string;
|
||||
persist(): void;
|
||||
}
|
||||
|
||||
declare class SyntheticAnimationEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticEvent<T> {
|
||||
animationName: string;
|
||||
elapsedTime: number;
|
||||
pseudoElement: string;
|
||||
}
|
||||
|
||||
declare class SyntheticClipboardEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticEvent<T> {
|
||||
clipboardData: any;
|
||||
}
|
||||
|
||||
declare class SyntheticCompositionEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticEvent<T> {
|
||||
data: any;
|
||||
}
|
||||
|
||||
declare class SyntheticInputEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticEvent<T> {
|
||||
+target: HTMLInputElement;
|
||||
data: any;
|
||||
}
|
||||
|
||||
declare class SyntheticUIEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
+E: Event = Event,
|
||||
> extends SyntheticEvent<T, E> {
|
||||
detail: number;
|
||||
view: any;
|
||||
}
|
||||
|
||||
declare class SyntheticFocusEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticUIEvent<T> {
|
||||
relatedTarget: EventTarget;
|
||||
}
|
||||
|
||||
declare class SyntheticKeyboardEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticUIEvent<T, KeyboardEvent> {
|
||||
altKey: boolean;
|
||||
charCode: number;
|
||||
ctrlKey: boolean;
|
||||
getModifierState(keyArg?: string): boolean;
|
||||
key: string;
|
||||
keyCode: number;
|
||||
locale: string;
|
||||
location: number;
|
||||
metaKey: boolean;
|
||||
repeat: boolean;
|
||||
shiftKey: boolean;
|
||||
which: number;
|
||||
}
|
||||
|
||||
declare class SyntheticMouseEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
+E: Event = MouseEvent,
|
||||
> extends SyntheticUIEvent<T, E> {
|
||||
altKey: boolean;
|
||||
button: number;
|
||||
buttons: number;
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
ctrlKey: boolean;
|
||||
getModifierState(keyArg: string): boolean;
|
||||
metaKey: boolean;
|
||||
pageX: number;
|
||||
pageY: number;
|
||||
relatedTarget: EventTarget;
|
||||
screenX: number;
|
||||
screenY: number;
|
||||
shiftKey: boolean;
|
||||
}
|
||||
|
||||
declare class SyntheticDragEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticMouseEvent<T, DragEvent> {
|
||||
dataTransfer: any;
|
||||
}
|
||||
|
||||
declare class SyntheticWheelEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticMouseEvent<T, WheelEvent> {
|
||||
deltaMode: number;
|
||||
deltaX: number;
|
||||
deltaY: number;
|
||||
deltaZ: number;
|
||||
}
|
||||
|
||||
declare class SyntheticPointerEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticMouseEvent<T, PointerEvent> {
|
||||
pointerId: number;
|
||||
width: number;
|
||||
height: number;
|
||||
pressure: number;
|
||||
tangentialPressure: number;
|
||||
tiltX: number;
|
||||
tiltY: number;
|
||||
twist: number;
|
||||
pointerType: string;
|
||||
isPrimary: boolean;
|
||||
}
|
||||
|
||||
declare class SyntheticTouchEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticUIEvent<T, TouchEvent> {
|
||||
altKey: boolean;
|
||||
changedTouches: any;
|
||||
ctrlKey: boolean;
|
||||
getModifierState: any;
|
||||
metaKey: boolean;
|
||||
shiftKey: boolean;
|
||||
targetTouches: any;
|
||||
touches: any;
|
||||
}
|
||||
|
||||
declare class SyntheticTransitionEvent<
|
||||
+T: EventTarget = EventTarget,
|
||||
> extends SyntheticEvent<T> {
|
||||
propertyName: string;
|
||||
elapsedTime: number;
|
||||
pseudoElement: string;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
declare type $JSXIntrinsics = {
|
||||
// Catch-all for custom elements.
|
||||
[string]: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
// HTML
|
||||
a: {
|
||||
instance: HTMLAnchorElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
abbr: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
address: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
area: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
article: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
aside: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
audio: {
|
||||
instance: HTMLAudioElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
b: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
base: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
bdi: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
bdo: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
big: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
blockquote: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
body: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
br: {
|
||||
instance: HTMLBRElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
button: {
|
||||
instance: HTMLButtonElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
canvas: {
|
||||
instance: HTMLCanvasElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
caption: {
|
||||
instance: HTMLTableCaptionElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
cite: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
code: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
col: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
colgroup: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
data: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
datalist: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
dd: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
del: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
details: {
|
||||
instance: HTMLDetailsElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
dfn: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
dialog: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
div: {
|
||||
instance: HTMLDivElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
dl: {
|
||||
instance: HTMLDListElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
dt: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
em: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
embed: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
fieldset: {
|
||||
instance: HTMLFieldSetElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
figcaption: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
figure: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
footer: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
form: {
|
||||
instance: HTMLFormElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
h1: {
|
||||
instance: HTMLHeadingElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
h2: {
|
||||
instance: HTMLHeadingElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
h3: {
|
||||
instance: HTMLHeadingElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
h4: {
|
||||
instance: HTMLHeadingElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
h5: {
|
||||
instance: HTMLHeadingElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
h6: {
|
||||
instance: HTMLHeadingElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
head: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
header: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
hgroup: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
hr: {
|
||||
instance: HTMLHRElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
html: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
i: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
iframe: {
|
||||
instance: HTMLIFrameElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
img: {
|
||||
instance: HTMLImageElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
ins: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
kbd: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
keygen: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
label: {
|
||||
instance: HTMLLabelElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
legend: {
|
||||
instance: HTMLLegendElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
li: {
|
||||
instance: HTMLLIElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
link: {
|
||||
instance: HTMLLinkElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
main: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
map: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
mark: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
menu: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
menuitem: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
meta: {
|
||||
instance: HTMLMetaElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
meter: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
nav: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
noscript: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
object: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
ol: {
|
||||
instance: HTMLOListElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
optgroup: {
|
||||
instance: HTMLOptGroupElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
option: {
|
||||
instance: HTMLOptionElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
output: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
p: {
|
||||
instance: HTMLParagraphElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
param: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
picture: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
pre: {
|
||||
instance: HTMLPreElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
progress: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
q: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
rp: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
rt: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
ruby: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
s: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
samp: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
script: {
|
||||
instance: HTMLScriptElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
section: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
small: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
source: {
|
||||
instance: HTMLSourceElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
span: {
|
||||
instance: HTMLSpanElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
strong: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
style: {
|
||||
instance: HTMLStyleElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
sub: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
summary: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
sup: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
table: {
|
||||
instance: HTMLTableElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
tbody: {
|
||||
instance: HTMLTableSectionElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
td: {
|
||||
instance: HTMLTableCellElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
tfoot: {
|
||||
instance: HTMLTableSectionElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
th: {
|
||||
instance: HTMLTableCellElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
thead: {
|
||||
instance: HTMLTableSectionElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
time: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
title: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
tr: {
|
||||
instance: HTMLTableRowElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
track: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
u: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
ul: {
|
||||
instance: HTMLUListElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
'var': ReactDOM$HTMLElementJSXIntrinsic,
|
||||
video: {
|
||||
instance: HTMLVideoElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
wbr: ReactDOM$HTMLElementJSXIntrinsic,
|
||||
// SVG
|
||||
svg: ReactDOM$SVGElementJSXIntrinsic,
|
||||
animate: ReactDOM$SVGElementJSXIntrinsic,
|
||||
circle: ReactDOM$SVGElementJSXIntrinsic,
|
||||
defs: ReactDOM$SVGElementJSXIntrinsic,
|
||||
ellipse: ReactDOM$SVGElementJSXIntrinsic,
|
||||
g: ReactDOM$SVGElementJSXIntrinsic,
|
||||
image: ReactDOM$SVGElementJSXIntrinsic,
|
||||
line: ReactDOM$SVGElementJSXIntrinsic,
|
||||
linearGradient: ReactDOM$SVGElementJSXIntrinsic,
|
||||
mask: ReactDOM$SVGElementJSXIntrinsic,
|
||||
path: ReactDOM$SVGElementJSXIntrinsic,
|
||||
pattern: ReactDOM$SVGElementJSXIntrinsic,
|
||||
polygon: ReactDOM$SVGElementJSXIntrinsic,
|
||||
polyline: ReactDOM$SVGElementJSXIntrinsic,
|
||||
radialGradient: ReactDOM$SVGElementJSXIntrinsic,
|
||||
rect: ReactDOM$SVGElementJSXIntrinsic,
|
||||
stop: ReactDOM$SVGElementJSXIntrinsic,
|
||||
symbol: ReactDOM$SVGElementJSXIntrinsic,
|
||||
text: ReactDOM$SVGElementJSXIntrinsic,
|
||||
tspan: ReactDOM$SVGElementJSXIntrinsic,
|
||||
use: ReactDOM$SVGElementJSXIntrinsic,
|
||||
// Elements React adds extra props for.
|
||||
input: {
|
||||
instance: HTMLInputElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
textarea: {
|
||||
instance: HTMLTextAreaElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
select: {
|
||||
instance: HTMLSelectElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
...
|
||||
};
|
||||
|
||||
type ReactDOM$HTMLElementJSXIntrinsic = {
|
||||
instance: HTMLElement,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
};
|
||||
|
||||
type ReactDOM$SVGElementJSXIntrinsic = {
|
||||
instance: Element,
|
||||
props: {
|
||||
[key: string]: any,
|
||||
children?: React$Node,
|
||||
...
|
||||
},
|
||||
...
|
||||
};
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
type FrameType = 'auxiliary' | 'top-level' | 'nested' | 'none';
|
||||
type VisibilityState = 'hidden' | 'visible' | 'prerender' | 'unloaded';
|
||||
|
||||
declare class WindowClient extends Client {
|
||||
visibilityState: VisibilityState,
|
||||
focused: boolean,
|
||||
focus(): Promise<WindowClient>,
|
||||
navigate(url: string): Promise<WindowClient>,
|
||||
}
|
||||
|
||||
declare class Client {
|
||||
id: string,
|
||||
reserved: boolean,
|
||||
url: string,
|
||||
frameType: FrameType,
|
||||
postMessage(message: any, transfer?: Iterator<any> | Array<any>): void,
|
||||
}
|
||||
|
||||
declare class ExtendableEvent extends Event {
|
||||
waitUntil(f: Promise<mixed>): void,
|
||||
}
|
||||
|
||||
type ForeignFetchOptions = {
|
||||
scopes: Iterator<string>,
|
||||
origins: Iterator<string>,
|
||||
...
|
||||
};
|
||||
|
||||
declare class InstallEvent extends ExtendableEvent {
|
||||
registerForeignFetch(options: ForeignFetchOptions): void,
|
||||
}
|
||||
|
||||
declare class FetchEvent extends ExtendableEvent {
|
||||
request: Request,
|
||||
clientId: string,
|
||||
isReload: boolean,
|
||||
respondWith(response: Response | Promise<Response>): void,
|
||||
preloadResponse: Promise<?Response>,
|
||||
}
|
||||
|
||||
type ClientType = 'window' | 'worker' | 'sharedworker' | 'all';
|
||||
type ClientQueryOptions = {
|
||||
includeUncontrolled?: boolean,
|
||||
includeReserved?: boolean,
|
||||
type?: ClientType,
|
||||
...
|
||||
};
|
||||
|
||||
declare class Clients {
|
||||
get(id: string): Promise<?Client>,
|
||||
matchAll(options?: ClientQueryOptions): Promise<Array<Client>>,
|
||||
openWindow(url: string): Promise<?WindowClient>,
|
||||
claim(): Promise<void>,
|
||||
}
|
||||
|
||||
type ServiceWorkerState = 'installing'
|
||||
| 'installed'
|
||||
| 'activating'
|
||||
| 'activated'
|
||||
| 'redundant';
|
||||
|
||||
declare class ServiceWorker extends EventTarget {
|
||||
scriptURL: string,
|
||||
state: ServiceWorkerState,
|
||||
|
||||
postMessage(message: any, transfer?: Iterator<any>): void,
|
||||
|
||||
onstatechange?: EventHandler,
|
||||
}
|
||||
|
||||
declare class NavigationPreloadState {
|
||||
enabled: boolean,
|
||||
headerValue: string,
|
||||
}
|
||||
|
||||
declare class NavigationPreloadManager {
|
||||
enable: Promise<void>,
|
||||
disable: Promise<void>,
|
||||
setHeaderValue(value: string): Promise<void>,
|
||||
getState: Promise<NavigationPreloadState>,
|
||||
}
|
||||
|
||||
type PushSubscriptionOptions = {
|
||||
userVisibleOnly?: boolean,
|
||||
applicationServerKey?: string | ArrayBuffer | $ArrayBufferView,
|
||||
...
|
||||
}
|
||||
|
||||
declare class PushSubscriptionJSON {
|
||||
endpoint: string,
|
||||
expirationTime: number | null,
|
||||
keys: { [string]: string, ... };
|
||||
}
|
||||
|
||||
declare class PushSubscription {
|
||||
+endpoint: string,
|
||||
+expirationTime: number | null,
|
||||
+options: PushSubscriptionOptions,
|
||||
getKey(name: string): ArrayBuffer | null,
|
||||
toJSON(): PushSubscriptionJSON,
|
||||
unsubscribe(): Promise<boolean>,
|
||||
}
|
||||
|
||||
declare class PushManager {
|
||||
+supportedContentEncodings: Array<string>,
|
||||
subscribe(options?: PushSubscriptionOptions): Promise<PushSubscription>,
|
||||
getSubscription(): Promise<PushSubscription | null>,
|
||||
permissionState(options?: PushSubscriptionOptions): Promise<'granted' | 'denied' | 'prompt'>,
|
||||
}
|
||||
|
||||
type ServiceWorkerUpdateViaCache = 'imports' | 'all' | 'none';
|
||||
|
||||
declare class ServiceWorkerRegistration extends EventTarget {
|
||||
+installing: ?ServiceWorker,
|
||||
+waiting: ?ServiceWorker,
|
||||
+active: ?ServiceWorker,
|
||||
+navigationPreload: NavigationPreloadManager,
|
||||
+scope: string,
|
||||
+updateViaCache: ServiceWorkerUpdateViaCache,
|
||||
+pushManager: PushManager,
|
||||
|
||||
update(): Promise<void>,
|
||||
unregister(): Promise<boolean>,
|
||||
|
||||
onupdatefound?: EventHandler,
|
||||
}
|
||||
|
||||
type WorkerType = 'classic' | 'module';
|
||||
|
||||
type RegistrationOptions = {
|
||||
scope?: string,
|
||||
type?: WorkerType,
|
||||
updateViaCache?: ServiceWorkerUpdateViaCache,
|
||||
...
|
||||
};
|
||||
|
||||
declare class ServiceWorkerContainer extends EventTarget {
|
||||
+controller: ?ServiceWorker,
|
||||
+ready: Promise<ServiceWorkerRegistration>,
|
||||
|
||||
getRegistration(clientURL?: string): Promise<ServiceWorkerRegistration | void>,
|
||||
getRegistrations(): Promise<Iterator<ServiceWorkerRegistration>>,
|
||||
register(
|
||||
scriptURL: string,
|
||||
options?: RegistrationOptions
|
||||
): Promise<ServiceWorkerRegistration>,
|
||||
startMessages(): void,
|
||||
|
||||
oncontrollerchange?: EventHandler,
|
||||
onmessage?: EventHandler,
|
||||
onmessageerror?: EventHandler,
|
||||
}
|
||||
|
||||
/**
|
||||
* This feature has been removed from the Web standards.
|
||||
*/
|
||||
declare class ServiceWorkerMessageEvent extends Event {
|
||||
data: any,
|
||||
lastEventId: string,
|
||||
origin: string,
|
||||
ports: Array<MessagePort>,
|
||||
source: ?(ServiceWorker | MessagePort),
|
||||
}
|
||||
|
||||
declare class ExtendableMessageEvent extends ExtendableEvent {
|
||||
data: any,
|
||||
lastEventId: string,
|
||||
origin: string,
|
||||
ports: Array<MessagePort>,
|
||||
source: ?(ServiceWorker | MessagePort),
|
||||
}
|
||||
|
||||
type CacheQueryOptions = {
|
||||
ignoreSearch?: boolean,
|
||||
ignoreMethod?: boolean,
|
||||
ignoreVary?: boolean,
|
||||
cacheName?: string,
|
||||
...
|
||||
}
|
||||
|
||||
declare class Cache {
|
||||
match(request: RequestInfo, options?: CacheQueryOptions): Promise<Response>,
|
||||
matchAll(
|
||||
request: RequestInfo,
|
||||
options?: CacheQueryOptions
|
||||
): Promise<Array<Response>>,
|
||||
add(request: RequestInfo): Promise<void>,
|
||||
addAll(requests: Array<RequestInfo>): Promise<void>,
|
||||
put(request: RequestInfo, response: Response): Promise<void>,
|
||||
delete(request: RequestInfo, options?: CacheQueryOptions): Promise<boolean>,
|
||||
keys(
|
||||
request?: RequestInfo,
|
||||
options?: CacheQueryOptions
|
||||
): Promise<Array<Request>>,
|
||||
}
|
||||
|
||||
declare class CacheStorage {
|
||||
match(request: RequestInfo, options?: CacheQueryOptions): Promise<Response>,
|
||||
has(cacheName: string): Promise<true>,
|
||||
open(cacheName: string): Promise<Cache>,
|
||||
delete(cacheName: string): Promise<boolean>,
|
||||
keys(): Promise<Array<string>>,
|
||||
}
|
||||
|
||||
// Service worker global scope
|
||||
// https://www.w3.org/TR/service-workers/#service-worker-global-scope
|
||||
declare var clients: Clients;
|
||||
declare var caches: CacheStorage;
|
||||
declare var registration: ServiceWorkerRegistration;
|
||||
declare function skipWaiting(): Promise<void>;
|
||||
declare var onactivate: ?EventHandler;
|
||||
declare var oninstall: ?EventHandler;
|
||||
declare var onfetch: ?EventHandler;
|
||||
declare var onforeignfetch: ?EventHandler;
|
||||
declare var onmessage: ?EventHandler;
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
type TextEncodeOptions = { options?: boolean, ... };
|
||||
|
||||
declare class TextEncoder {
|
||||
encode(buffer: string, options?: TextEncodeOptions): Uint8Array,
|
||||
}
|
||||
|
||||
declare class ReadableStreamController {
|
||||
constructor(
|
||||
stream: ReadableStream,
|
||||
underlyingSource: UnderlyingSource,
|
||||
size: number,
|
||||
highWaterMark: number,
|
||||
): void,
|
||||
|
||||
desiredSize: number,
|
||||
|
||||
close(): void,
|
||||
enqueue(chunk: any): void,
|
||||
error(error: Error): void,
|
||||
}
|
||||
|
||||
declare class ReadableStreamBYOBRequest {
|
||||
constructor(controller: ReadableStreamController, view: $TypedArray): void,
|
||||
|
||||
view: $TypedArray,
|
||||
|
||||
respond(bytesWritten: number): ?any,
|
||||
respondWithNewView(view: $TypedArray): ?any,
|
||||
}
|
||||
|
||||
declare class ReadableByteStreamController extends ReadableStreamController {
|
||||
constructor(
|
||||
stream: ReadableStream,
|
||||
underlyingSource: UnderlyingSource,
|
||||
highWaterMark: number,
|
||||
): void,
|
||||
|
||||
byobRequest: ReadableStreamBYOBRequest,
|
||||
}
|
||||
|
||||
declare class ReadableStreamReader {
|
||||
constructor(stream: ReadableStream): void,
|
||||
|
||||
closed: boolean,
|
||||
|
||||
cancel(reason: string): void,
|
||||
read(): Promise<{
|
||||
value: ?any,
|
||||
done: boolean,
|
||||
...
|
||||
}>,
|
||||
releaseLock(): void,
|
||||
}
|
||||
|
||||
declare interface UnderlyingSource {
|
||||
autoAllocateChunkSize?: number,
|
||||
type?: string,
|
||||
|
||||
start?: (controller: ReadableStreamController) => ?Promise<void>,
|
||||
pull?: (controller: ReadableStreamController) => ?Promise<void>,
|
||||
cancel?: (reason: string) => ?Promise<void>,
|
||||
}
|
||||
|
||||
declare class TransformStream {
|
||||
readable: ReadableStream,
|
||||
writable: WritableStream,
|
||||
};
|
||||
|
||||
type PipeToOptions = {
|
||||
preventClose?: boolean,
|
||||
preventAbort?: boolean,
|
||||
preventCancel?: boolean,
|
||||
...
|
||||
};
|
||||
|
||||
type QueuingStrategy = {
|
||||
highWaterMark: number,
|
||||
size(chunk: ?any): number,
|
||||
...
|
||||
};
|
||||
|
||||
declare class ReadableStream {
|
||||
constructor(
|
||||
underlyingSource: ?UnderlyingSource,
|
||||
queuingStrategy: ?QueuingStrategy,
|
||||
): void,
|
||||
|
||||
locked: boolean,
|
||||
|
||||
cancel(reason: string): void,
|
||||
getReader(): ReadableStreamReader,
|
||||
pipeThrough(transform: TransformStream, options: ?any): void,
|
||||
pipeTo(dest: WritableStream, options: ?PipeToOptions): Promise<void>,
|
||||
tee(): [ReadableStream, ReadableStream],
|
||||
};
|
||||
|
||||
declare interface WritableStreamController {
|
||||
error(error: Error): void,
|
||||
}
|
||||
|
||||
declare interface UnderlyingSink {
|
||||
autoAllocateChunkSize?: number,
|
||||
type?: string,
|
||||
|
||||
abort?: (reason: string) => ?Promise<void>,
|
||||
close?: (controller: WritableStreamController) => ?Promise<void>,
|
||||
start?: (controller: WritableStreamController) => ?Promise<void>,
|
||||
write?: (chunk: any, controller: WritableStreamController) => ?Promise<void>,
|
||||
}
|
||||
|
||||
declare interface WritableStreamWriter {
|
||||
closed: Promise<any>,
|
||||
desiredSize?: number,
|
||||
ready: Promise<any>,
|
||||
|
||||
abort(reason: string): ?Promise<any>,
|
||||
close(): Promise<any>,
|
||||
releaseLock(): void,
|
||||
write(chunk: any): Promise<any>,
|
||||
}
|
||||
|
||||
declare class WritableStream {
|
||||
constructor(
|
||||
underlyingSink: ?UnderlyingSink,
|
||||
queuingStrategy: QueuingStrategy,
|
||||
): void,
|
||||
|
||||
locked: boolean,
|
||||
|
||||
abort(reason: string): void,
|
||||
getWriter(): WritableStreamWriter,
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// https://github.com/WebAssembly/design/blob/master/JS.md
|
||||
// https://developer.mozilla.org/en-US/docs/WebAssembly
|
||||
// https://github.com/WebAssembly/design/blob/master/Web.md
|
||||
|
||||
type BufferSource = $TypedArray | ArrayBuffer;
|
||||
type ImportExportKind = 'function' | 'table' | 'memory' | 'global';
|
||||
type ImportObject = Object;
|
||||
type ResultObject = {
|
||||
module: WebAssembly$Module,
|
||||
instance: WebAssembly$Instance,
|
||||
...
|
||||
};
|
||||
|
||||
// https://github.com/WebAssembly/design/blob/master/JS.md#exported-function-exotic-objects
|
||||
declare class ExportedFunctionExoticObject extends Function {
|
||||
(): mixed;
|
||||
}
|
||||
|
||||
declare class WebAssembly$Module {
|
||||
constructor(bufferSource: BufferSource): void;
|
||||
|
||||
static exports(moduleObject: WebAssembly$Module): Array<{
|
||||
name: string,
|
||||
kind: ImportExportKind,
|
||||
...
|
||||
}>;
|
||||
static imports(moduleObject: WebAssembly$Module): Array<{
|
||||
name: string,
|
||||
name: string,
|
||||
kind: ImportExportKind,
|
||||
...
|
||||
}>;
|
||||
static customSections(moduleObject: WebAssembly$Module, sectionName: string): Array<ArrayBuffer>;
|
||||
}
|
||||
|
||||
declare class WebAssembly$Instance {
|
||||
constructor(moduleObject: WebAssembly$Module, importObject?: ImportObject): void;
|
||||
|
||||
+exports: { [exportedFunction: string]: ExportedFunctionExoticObject, ... };
|
||||
}
|
||||
|
||||
type MemoryDescriptor = {
|
||||
initial: number,
|
||||
maximum?: number,
|
||||
...
|
||||
};
|
||||
|
||||
declare class WebAssembly$Memory {
|
||||
constructor(memoryDescriptor: MemoryDescriptor): void;
|
||||
|
||||
+buffer: ArrayBuffer;
|
||||
|
||||
grow(delta: number): number;
|
||||
}
|
||||
|
||||
type TableDescriptor = {
|
||||
element: 'anyfunc',
|
||||
initial: number,
|
||||
maximum?: number,
|
||||
...
|
||||
};
|
||||
|
||||
declare class WebAssembly$Table {
|
||||
constructor(tableDescriptor: TableDescriptor): void;
|
||||
|
||||
+length: number;
|
||||
|
||||
grow(delta: number): number;
|
||||
get(index: number): ExportedFunctionExoticObject;
|
||||
set(index: number, value: ExportedFunctionExoticObject): void;
|
||||
}
|
||||
|
||||
declare class WebAssembly$CompileError extends Error {}
|
||||
declare class WebAssembly$LinkError extends Error {}
|
||||
declare class WebAssembly$RuntimeError extends Error {}
|
||||
|
||||
declare function WebAssembly$instantiate(bufferSource: BufferSource, importObject?: ImportObject): Promise<ResultObject>;
|
||||
declare function WebAssembly$instantiate(moduleObject: WebAssembly$Module, importObject?: ImportObject): Promise<WebAssembly$Instance>;
|
||||
|
||||
declare var WebAssembly: {
|
||||
Module: typeof WebAssembly$Module,
|
||||
Instance: typeof WebAssembly$Instance,
|
||||
Memory: typeof WebAssembly$Memory,
|
||||
Table: typeof WebAssembly$Table,
|
||||
CompileError: typeof WebAssembly$CompileError,
|
||||
LinkError: typeof WebAssembly$LinkError,
|
||||
RuntimeError: typeof WebAssembly$RuntimeError,
|
||||
validate(bufferSource: BufferSource): boolean,
|
||||
compile(bufferSource: BufferSource): Promise<WebAssembly$Module>,
|
||||
instantiate: typeof WebAssembly$instantiate,
|
||||
// web embedding API
|
||||
compileStreaming(source: Response | Promise<Response>): Promise<WebAssembly$Module>,
|
||||
instantiateStreaming(source: Response | Promise<Response>, importObject?: ImportObject): Promise<ResultObject>,
|
||||
...
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Add $FlowFixMe comments to lines with Flow errors that can't be auto-fixed.
|
||||
This script is idempotent - running it multiple times produces the same result.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
APP_DIR = os.environ.get('APP_DIR', os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
def get_flow_errors():
|
||||
"""Run flow and get errors as JSON."""
|
||||
result = subprocess.run(
|
||||
['npx', 'flow', '--json', '--show-all-errors'],
|
||||
capture_output=True, text=True, cwd=APP_DIR,
|
||||
timeout=300
|
||||
)
|
||||
|
||||
output = result.stdout
|
||||
json_start = output.find('{"flowVersion"')
|
||||
if json_start == -1:
|
||||
json_start = result.stderr.find('{"flowVersion"')
|
||||
if json_start != -1:
|
||||
output = result.stderr
|
||||
else:
|
||||
print("ERROR: Could not find JSON in flow output")
|
||||
return []
|
||||
|
||||
json_str = output[json_start:]
|
||||
try:
|
||||
data = json.loads(json_str)
|
||||
return data.get('errors', [])
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"ERROR: JSON parse error: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def add_flowfixme_comments(errors):
|
||||
"""Add $FlowFixMe comments to files with errors."""
|
||||
# Group errors by file and line, collecting ALL error codes per line
|
||||
file_errors = defaultdict(lambda: defaultdict(set))
|
||||
|
||||
for error in errors:
|
||||
messages = error.get('message', [])
|
||||
error_codes = error.get('error_codes', [])
|
||||
|
||||
if not messages:
|
||||
continue
|
||||
|
||||
primary = messages[0]
|
||||
loc = primary.get('loc', {})
|
||||
source = loc.get('source', '')
|
||||
line = loc.get('start', {}).get('line', 0)
|
||||
|
||||
if not source or not line:
|
||||
continue
|
||||
|
||||
# Only fix files in src/ directory of the app
|
||||
if APP_DIR + '/src/' not in source:
|
||||
continue
|
||||
|
||||
error_code = error_codes[0] if error_codes else 'incompatible-type'
|
||||
# Never rely on signature-verification-failure suppressions, as this
|
||||
# degrades typed exports/imports too much.
|
||||
if error_code == 'signature-verification-failure':
|
||||
error_code = 'incompatible-type'
|
||||
file_errors[source][line].add(error_code)
|
||||
|
||||
files_modified = 0
|
||||
comments_added = 0
|
||||
|
||||
for filepath, line_errors in sorted(file_errors.items()):
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(filepath, 'r') as f:
|
||||
lines = f.readlines()
|
||||
except (IOError, UnicodeDecodeError):
|
||||
continue
|
||||
|
||||
# Sort lines in reverse order so we can insert without offset issues
|
||||
sorted_lines = sorted(line_errors.keys(), reverse=True)
|
||||
modified = False
|
||||
|
||||
for line_num in sorted_lines:
|
||||
error_codes = line_errors[line_num]
|
||||
idx = line_num - 1 # 0-indexed
|
||||
|
||||
if idx < 0 or idx >= len(lines):
|
||||
continue
|
||||
|
||||
# Collect existing $FlowFixMe codes above this line
|
||||
existing_codes = set()
|
||||
check_idx = idx - 1
|
||||
while check_idx >= 0 and '$FlowFixMe' in lines[check_idx]:
|
||||
# Extract the code from existing $FlowFixMe comment
|
||||
match = re.search(r'\$FlowFixMe\[([^\]]+)\]', lines[check_idx])
|
||||
if match:
|
||||
existing_codes.add(match.group(1))
|
||||
check_idx -= 1
|
||||
|
||||
# Also check if current line has inline $FlowFixMe
|
||||
if '$FlowFixMe' in lines[idx]:
|
||||
match = re.search(r'\$FlowFixMe\[([^\]]+)\]', lines[idx])
|
||||
if match:
|
||||
existing_codes.add(match.group(1))
|
||||
|
||||
# Determine which codes need to be added
|
||||
needed_codes = error_codes - existing_codes
|
||||
|
||||
if not needed_codes:
|
||||
continue
|
||||
|
||||
# Get the indentation of the error line
|
||||
current_line = lines[idx]
|
||||
indent = ''
|
||||
for ch in current_line:
|
||||
if ch in (' ', '\t'):
|
||||
indent += ch
|
||||
else:
|
||||
break
|
||||
|
||||
# Add $FlowFixMe comments for each needed code
|
||||
for code in sorted(needed_codes):
|
||||
comment = f"{indent}// $FlowFixMe[{code}]\n"
|
||||
lines.insert(idx, comment)
|
||||
modified = True
|
||||
comments_added += 1
|
||||
|
||||
if modified:
|
||||
with open(filepath, 'w') as f:
|
||||
f.writelines(lines)
|
||||
files_modified += 1
|
||||
|
||||
print(f" Added {comments_added} $FlowFixMe comments across {files_modified} files")
|
||||
return comments_added
|
||||
|
||||
|
||||
def main():
|
||||
print(" Getting Flow errors...")
|
||||
errors = get_flow_errors()
|
||||
print(f" Found {len(errors)} errors total")
|
||||
|
||||
if not errors:
|
||||
print(" No errors to fix!")
|
||||
return 0
|
||||
|
||||
src_errors = 0
|
||||
for error in errors:
|
||||
messages = error.get('message', [])
|
||||
if messages:
|
||||
loc = messages[0].get('loc', {})
|
||||
source = loc.get('source', '')
|
||||
if APP_DIR + '/src/' in source:
|
||||
src_errors += 1
|
||||
|
||||
print(f" Found {src_errors} errors in src/ files")
|
||||
|
||||
if src_errors == 0:
|
||||
print(" No src/ errors to fix!")
|
||||
return 0
|
||||
|
||||
count = add_flowfixme_comments(errors)
|
||||
return count
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
added = main()
|
||||
sys.exit(0 if added >= 0 else 1)
|
||||
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Convert // $FlowFixMe[code] comments that are inside JSX to {/* $FlowFixMe[code] */} format.
|
||||
This is needed because // comments don't work inside JSX element content.
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
APP_DIR = os.environ.get('APP_DIR', os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
def looks_like_jsx_context(lines, code_idx):
|
||||
"""
|
||||
Heuristic to detect if a line is inside JSX where // comments are invalid.
|
||||
"""
|
||||
if code_idx < 0 or code_idx >= len(lines):
|
||||
return False
|
||||
|
||||
for idx in range(code_idx, min(len(lines), code_idx + 4)):
|
||||
stripped = lines[idx].lstrip()
|
||||
if not stripped:
|
||||
continue
|
||||
if stripped.startswith('//') or stripped.startswith('/*') or stripped.startswith('*'):
|
||||
continue
|
||||
|
||||
if (
|
||||
stripped.startswith('<')
|
||||
or stripped.startswith('</')
|
||||
or stripped.startswith('<>')
|
||||
or stripped.startswith('{<')
|
||||
or stripped.startswith('(<')
|
||||
):
|
||||
return True
|
||||
|
||||
# JSX expression containers can start with "{" and contain JSX on
|
||||
# following lines:
|
||||
# {
|
||||
# condition && (
|
||||
# <Component />
|
||||
# )
|
||||
# }
|
||||
if stripped.startswith('{'):
|
||||
for look_ahead in range(idx + 1, min(len(lines), idx + 5)):
|
||||
next_stripped = lines[look_ahead].lstrip()
|
||||
if not next_stripped:
|
||||
continue
|
||||
if (
|
||||
next_stripped.startswith('<')
|
||||
or next_stripped.startswith('</')
|
||||
or next_stripped.startswith('<>')
|
||||
):
|
||||
return True
|
||||
if (
|
||||
next_stripped.startswith('//')
|
||||
or next_stripped.startswith('/*')
|
||||
or next_stripped.startswith('*')
|
||||
):
|
||||
continue
|
||||
break
|
||||
|
||||
# Common pattern:
|
||||
# {
|
||||
# condition && (
|
||||
# <Component />
|
||||
# )
|
||||
# }
|
||||
if stripped.startswith('(') and idx + 1 < len(lines):
|
||||
next_stripped = lines[idx + 1].lstrip()
|
||||
if (
|
||||
next_stripped.startswith('<')
|
||||
or next_stripped.startswith('</')
|
||||
or next_stripped.startswith('<>')
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_flow_errors():
|
||||
"""Run flow and get errors as JSON."""
|
||||
result = subprocess.run(
|
||||
['npx', 'flow', '--json', '--show-all-errors'],
|
||||
capture_output=True, text=True, cwd=APP_DIR,
|
||||
timeout=300
|
||||
)
|
||||
output = result.stdout
|
||||
idx = output.find('{"flowVersion"')
|
||||
if idx == -1:
|
||||
return []
|
||||
try:
|
||||
data = json.loads(output[idx:])
|
||||
return data.get('errors', [])
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
|
||||
|
||||
def main():
|
||||
errors = get_flow_errors()
|
||||
|
||||
# Find errors where $FlowFixMe is on the line above but not suppressing
|
||||
file_fixes = defaultdict(set)
|
||||
|
||||
for e in errors:
|
||||
msgs = e.get('message', [])
|
||||
codes = e.get('error_codes', [])
|
||||
if not msgs:
|
||||
continue
|
||||
loc = msgs[0].get('loc', {})
|
||||
source = loc.get('source', '')
|
||||
line = loc.get('start', {}).get('line', 0)
|
||||
if '/src/' not in source:
|
||||
continue
|
||||
code = codes[0] if codes else 'incompatible-type'
|
||||
|
||||
if not os.path.isfile(source):
|
||||
continue
|
||||
|
||||
with open(source) as f:
|
||||
lines = f.readlines()
|
||||
|
||||
if line < 2 or line > len(lines):
|
||||
continue
|
||||
|
||||
above = lines[line - 2]
|
||||
target = '// $FlowFixMe[' + code + ']'
|
||||
if target in above:
|
||||
file_fixes[source].add(line - 2)
|
||||
|
||||
# Convert the failing comments to JSX format
|
||||
fixes_count = 0
|
||||
reverted_count = 0
|
||||
for filepath, fixme_indices in file_fixes.items():
|
||||
with open(filepath) as f:
|
||||
lines = f.readlines()
|
||||
|
||||
modified = False
|
||||
converted_indices = set()
|
||||
for idx in sorted(fixme_indices):
|
||||
if idx >= len(lines):
|
||||
continue
|
||||
line = lines[idx]
|
||||
m = re.match(r'^(\s*)// (\$FlowFixMe\[[^\]]+\])(.*?)$', line.rstrip())
|
||||
if m:
|
||||
indent = m.group(1)
|
||||
fixme = m.group(2)
|
||||
rest = m.group(3).strip()
|
||||
if rest:
|
||||
lines[idx] = f'{indent}{{/* {fixme} {rest} */}}\n'
|
||||
else:
|
||||
lines[idx] = f'{indent}{{/* {fixme} */}}\n'
|
||||
modified = True
|
||||
fixes_count += 1
|
||||
converted_indices.add(idx)
|
||||
|
||||
# If a JSX-style FlowFixMe ended up outside JSX (can happen with noisy
|
||||
# parse errors), restore it back to normal // comment syntax.
|
||||
# Skip indices that were just converted from Flow error analysis -
|
||||
# those are known to be in JSX context even if the heuristic can't tell.
|
||||
for idx, line in enumerate(lines):
|
||||
if idx in converted_indices:
|
||||
continue
|
||||
m = re.match(r'^(\s*)\{/\*\s*(\$FlowFixMe\[[^\]]+\])(?:\s+(.*?))?\s*\*/\}\s*$', line.rstrip())
|
||||
if not m:
|
||||
continue
|
||||
if looks_like_jsx_context(lines, idx + 1):
|
||||
continue
|
||||
|
||||
indent = m.group(1)
|
||||
fixme = m.group(2)
|
||||
rest = (m.group(3) or '').strip()
|
||||
if rest:
|
||||
lines[idx] = f'{indent}// {fixme} {rest}\n'
|
||||
else:
|
||||
lines[idx] = f'{indent}// {fixme}\n'
|
||||
modified = True
|
||||
reverted_count += 1
|
||||
|
||||
if modified:
|
||||
with open(filepath, 'w') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
print(f"Converted {fixes_count} FlowFixMe comments to JSX format")
|
||||
if reverted_count:
|
||||
print(f"Reverted {reverted_count} non-JSX FlowFixMe comments back to // syntax")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fix arrow-function inline object return types that prettier 1.15 can't parse.
|
||||
|
||||
The Flow codemod may add return type annotations like:
|
||||
const foo = (): { prop: Type, ... } => { ... }
|
||||
|
||||
Prettier 1.15 cannot parse (): { ... } => because it confuses the opening
|
||||
brace of the return type with a function body.
|
||||
|
||||
This script detects files with prettier parse errors and, for each one,
|
||||
extracts the inline object return type into a named type alias:
|
||||
|
||||
type _FooReturnType = { prop: Type, ... };
|
||||
const foo = (): _FooReturnType => { ... }
|
||||
|
||||
The script is idempotent.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
APP_DIR = os.environ.get('APP_DIR', os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
SRC_DIR = os.path.join(APP_DIR, 'src')
|
||||
|
||||
|
||||
def get_prettier_errors():
|
||||
"""Run prettier --list-different and find files with syntax errors."""
|
||||
result = subprocess.run(
|
||||
['npx', 'prettier', '--list-different', 'src/!(locales)/**/*.js'],
|
||||
capture_output=True, text=True, cwd=APP_DIR, timeout=120
|
||||
)
|
||||
# Combine stdout and stderr to find error lines
|
||||
all_output = result.stdout + '\n' + result.stderr
|
||||
error_files = set()
|
||||
for line in all_output.split('\n'):
|
||||
m = re.match(r'\[error\]\s+(src/\S+\.js):\s+SyntaxError', line)
|
||||
if m:
|
||||
error_files.add(os.path.join(APP_DIR, m.group(1)))
|
||||
return error_files
|
||||
|
||||
|
||||
def find_brace_end(content, start):
|
||||
"""Find the matching closing brace for an opening brace at position start."""
|
||||
depth = 0
|
||||
i = start
|
||||
while i < len(content):
|
||||
ch = content[i]
|
||||
if ch == '{':
|
||||
depth += 1
|
||||
elif ch == '}':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return i
|
||||
elif ch == "'" or ch == '"' or ch == '`':
|
||||
# Skip strings
|
||||
quote = ch
|
||||
i += 1
|
||||
while i < len(content) and content[i] != quote:
|
||||
if content[i] == '\\':
|
||||
i += 1
|
||||
i += 1
|
||||
elif ch == '/' and i + 1 < len(content):
|
||||
if content[i + 1] == '/':
|
||||
# Line comment
|
||||
i = content.find('\n', i)
|
||||
if i == -1:
|
||||
return -1
|
||||
elif content[i + 1] == '*':
|
||||
# Block comment
|
||||
i = content.find('*/', i + 2)
|
||||
if i == -1:
|
||||
return -1
|
||||
i += 1
|
||||
i += 1
|
||||
return -1
|
||||
|
||||
|
||||
def extract_identifier_name(content, pos):
|
||||
"""Extract the identifier name before `= (` at the given position."""
|
||||
# Walk backwards from `= (` to find the variable/const name
|
||||
i = pos - 1
|
||||
while i >= 0 and content[i] in ' \t':
|
||||
i -= 1
|
||||
if i < 0 or content[i] != '=':
|
||||
return None
|
||||
i -= 1
|
||||
while i >= 0 and content[i] in ' \t':
|
||||
i -= 1
|
||||
if i < 0:
|
||||
return None
|
||||
end = i + 1
|
||||
while i >= 0 and (content[i].isalnum() or content[i] == '_' or content[i] == '$'):
|
||||
i -= 1
|
||||
name = content[i + 1:end]
|
||||
if name and name[0].isalpha():
|
||||
return name
|
||||
return None
|
||||
|
||||
|
||||
def fix_file(filepath):
|
||||
"""Fix arrow function return types in a single file."""
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
|
||||
original = content
|
||||
# Pattern: = (params): { ... } => {
|
||||
# We need to find ): { where { starts a type annotation, not a body
|
||||
# The type annotation ends with } =>
|
||||
pattern = re.compile(r'(=\s*\([^)]*\))\s*:\s*\{')
|
||||
|
||||
insertions = [] # (position, type_name, type_body) to insert before the line
|
||||
replacements = [] # (start, end, replacement)
|
||||
used_names = set()
|
||||
|
||||
for m in pattern.finditer(content):
|
||||
prefix = m.group(1) # e.g., '= (params)'
|
||||
brace_start = content.index('{', m.start() + len(prefix))
|
||||
brace_end = find_brace_end(content, brace_start)
|
||||
|
||||
if brace_end == -1:
|
||||
continue
|
||||
|
||||
# Check if followed by => { (arrow function body)
|
||||
after_brace = content[brace_end + 1:].lstrip()
|
||||
if not after_brace.startswith('=>'):
|
||||
continue
|
||||
|
||||
# Verify the type spans multiple lines (single-line types are ok for prettier)
|
||||
type_text = content[brace_start:brace_end + 1]
|
||||
if '\n' not in type_text:
|
||||
continue
|
||||
|
||||
# Get the identifier name for the type alias
|
||||
call_start = content.index('(', m.start())
|
||||
name = extract_identifier_name(content, call_start)
|
||||
if not name:
|
||||
name = 'Anonymous'
|
||||
|
||||
# Create a unique type name
|
||||
base_type_name = f'_{name[0].upper()}{name[1:]}ReturnType'
|
||||
type_name = base_type_name
|
||||
suffix = 2
|
||||
while type_name in used_names:
|
||||
type_name = f'{base_type_name}{suffix}'
|
||||
suffix += 1
|
||||
used_names.add(type_name)
|
||||
|
||||
# Find the line start for inserting the type declaration
|
||||
line_start = content.rfind('\n', 0, m.start())
|
||||
if line_start == -1:
|
||||
line_start = 0
|
||||
else:
|
||||
line_start += 1
|
||||
|
||||
insertions.append((line_start, type_name, type_text))
|
||||
|
||||
# Replace ): { ...type... } => with ): TypeName =>
|
||||
colon_pos = content.index(':', m.start() + len(prefix))
|
||||
replacements.append((colon_pos, brace_end + 1, f': {type_name}'))
|
||||
|
||||
if not replacements:
|
||||
return False
|
||||
|
||||
# Apply replacements in reverse order
|
||||
combined = list(zip(insertions, replacements))
|
||||
combined.sort(key=lambda x: x[1][0], reverse=True)
|
||||
|
||||
for (insert_pos, type_name, type_body), (rep_start, rep_end, rep_text) in combined:
|
||||
content = content[:rep_start] + rep_text + content[rep_end:]
|
||||
|
||||
# Now insert type declarations (from bottom to top to maintain positions)
|
||||
insertion_list = [(ins[0], ins[1], ins[2]) for ins, _ in combined]
|
||||
insertion_list.sort(key=lambda x: x[0], reverse=True)
|
||||
for insert_pos, type_name, type_body in insertion_list:
|
||||
type_decl = f'type {type_name} = {type_body};\n'
|
||||
content = content[:insert_pos] + type_decl + content[insert_pos:]
|
||||
|
||||
if content != original:
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
# First, find files that have prettier parse errors
|
||||
error_files = get_prettier_errors()
|
||||
|
||||
if not error_files:
|
||||
print(" No prettier parse errors found - no fixes needed.")
|
||||
return
|
||||
|
||||
print(f" Found {len(error_files)} files with prettier parse errors")
|
||||
|
||||
fixed = 0
|
||||
for filepath in sorted(error_files):
|
||||
if fix_file(filepath):
|
||||
fixed += 1
|
||||
print(f" Fixed: {os.path.relpath(filepath, SRC_DIR)}")
|
||||
|
||||
print(f" Fixed {fixed} files")
|
||||
|
||||
# Verify no more errors
|
||||
remaining = get_prettier_errors()
|
||||
if remaining:
|
||||
print(f" WARNING: {len(remaining)} files still have prettier parse errors:")
|
||||
for f in sorted(remaining):
|
||||
print(f" {os.path.relpath(f, SRC_DIR)}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,611 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Normalize Flow codemod syntax to Babel-compatible Flow syntax.
|
||||
|
||||
Flow 0.299 annotate-exports introduces syntax that older Babel/Flow tooling in this
|
||||
project cannot parse:
|
||||
- component(...) type annotations
|
||||
- renders type operator
|
||||
- as-casts
|
||||
|
||||
This script rewrites those constructs while preserving runtime behavior and as much
|
||||
type information as possible:
|
||||
- component(...) -> React.ComponentType<Props>
|
||||
- React.AbstractComponent<Props, Instance> -> React.ComponentType<{...Props, ref?: React.RefSetter<Instance>}>
|
||||
- renders T -> T (with renders any/mixed/empty/Fragment normalized to React.Node)
|
||||
- expr as T -> (expr: T)
|
||||
|
||||
The script is idempotent.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
APP_DIR = os.environ.get(
|
||||
"APP_DIR",
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
)
|
||||
SRC_DIR = pathlib.Path(APP_DIR) / "src"
|
||||
FLOW_BIN = pathlib.Path(APP_DIR) / "node_modules" / ".bin" / "flow"
|
||||
FLOW_AST_CMD = [str(FLOW_BIN), "ast"] if FLOW_BIN.exists() else ["npx", "flow", "ast"]
|
||||
|
||||
|
||||
def _extract_json(output: str) -> Optional[Dict[str, Any]]:
|
||||
start = output.find("{")
|
||||
if start == -1:
|
||||
return None
|
||||
try:
|
||||
return json.loads(output[start:])
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
|
||||
def _run_flow_ast(relative_path: str, content_text: str) -> Optional[Dict[str, Any]]:
|
||||
result = subprocess.run(
|
||||
FLOW_AST_CMD + ["--path", relative_path],
|
||||
input=content_text.encode("utf-8"),
|
||||
capture_output=True,
|
||||
cwd=APP_DIR,
|
||||
)
|
||||
stdout = result.stdout.decode("utf-8", errors="replace")
|
||||
stderr = result.stderr.decode("utf-8", errors="replace")
|
||||
data = _extract_json(stdout)
|
||||
if data is not None:
|
||||
return data
|
||||
# Flow may occasionally emit JSON to stderr on failure.
|
||||
return _extract_json(stderr)
|
||||
|
||||
|
||||
def _walk(node: Any, visit, parent_type: Optional[str] = None) -> None:
|
||||
if isinstance(node, dict):
|
||||
node_type = node.get("type")
|
||||
visit(node, parent_type)
|
||||
for value in node.values():
|
||||
_walk(value, visit, node_type)
|
||||
elif isinstance(node, list):
|
||||
for item in node:
|
||||
_walk(item, visit, parent_type)
|
||||
|
||||
|
||||
def _node_text(content: bytes, node: Dict[str, Any]) -> str:
|
||||
start, end = node["range"]
|
||||
return content[start:end].decode("utf-8")
|
||||
|
||||
|
||||
def _is_render_node_like(type_text: str) -> bool:
|
||||
normalized = "".join(type_text.split())
|
||||
if normalized in {
|
||||
"any",
|
||||
"mixed",
|
||||
"empty",
|
||||
"void",
|
||||
"null",
|
||||
"Fragment",
|
||||
"React.Node",
|
||||
"React$Node",
|
||||
"React.MixedElement",
|
||||
}:
|
||||
return True
|
||||
return (
|
||||
"React.Node" in normalized
|
||||
or "React$Node" in normalized
|
||||
or "React.MixedElement" in normalized
|
||||
)
|
||||
|
||||
|
||||
def _normalize_renders_target(type_text: str) -> str:
|
||||
stripped = type_text.strip()
|
||||
if stripped in {"any", "mixed", "empty", "Fragment"}:
|
||||
return "React.Node"
|
||||
return stripped
|
||||
|
||||
|
||||
def _object_property_key_name(property_node: Dict[str, Any]) -> Optional[str]:
|
||||
key = property_node.get("key")
|
||||
if not isinstance(key, dict):
|
||||
return None
|
||||
key_type = key.get("type")
|
||||
if key_type == "Identifier":
|
||||
return key.get("name")
|
||||
if key_type == "Literal":
|
||||
return key.get("value")
|
||||
return None
|
||||
|
||||
|
||||
def _object_type_has_ref_property(object_type_node: Dict[str, Any]) -> bool:
|
||||
for prop in object_type_node.get("properties", []):
|
||||
if not isinstance(prop, dict):
|
||||
continue
|
||||
if prop.get("type") != "ObjectTypeProperty":
|
||||
continue
|
||||
if _object_property_key_name(prop) == "ref":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _qualified_type_identifier_name(type_id_node: Any) -> Optional[str]:
|
||||
if not isinstance(type_id_node, dict):
|
||||
return None
|
||||
node_type = type_id_node.get("type")
|
||||
if node_type == "Identifier":
|
||||
return type_id_node.get("name")
|
||||
if node_type == "QualifiedTypeIdentifier":
|
||||
left = _qualified_type_identifier_name(type_id_node.get("qualification"))
|
||||
right = _qualified_type_identifier_name(type_id_node.get("id"))
|
||||
if left and right:
|
||||
return f"{left}.{right}"
|
||||
return right
|
||||
return None
|
||||
|
||||
|
||||
def _is_react_abstract_component_type(type_id_node: Any) -> bool:
|
||||
type_name = _qualified_type_identifier_name(type_id_node)
|
||||
return type_name in {"React.AbstractComponent", "React$AbstractComponent"}
|
||||
|
||||
|
||||
def _type_parameter_nodes(type_parameters_node: Any) -> List[Dict[str, Any]]:
|
||||
if not isinstance(type_parameters_node, dict):
|
||||
return []
|
||||
params = type_parameters_node.get("params")
|
||||
if not isinstance(params, list):
|
||||
return []
|
||||
return [param for param in params if isinstance(param, dict)]
|
||||
|
||||
|
||||
def _is_ref_setter_type(type_text: str) -> bool:
|
||||
stripped = type_text.strip()
|
||||
return stripped.startswith("React.RefSetter<") or stripped.startswith(
|
||||
"React$RefSetter<"
|
||||
)
|
||||
|
||||
|
||||
def _to_ref_setter_type(instance_type_text: str) -> str:
|
||||
stripped = instance_type_text.strip()
|
||||
if not stripped:
|
||||
return "React.RefSetter<mixed>"
|
||||
if _is_ref_setter_type(stripped):
|
||||
return stripped.replace("React$RefSetter", "React.RefSetter")
|
||||
return f"React.RefSetter<{stripped}>"
|
||||
|
||||
|
||||
def _props_text_has_ref_property(props_type_text: str) -> bool:
|
||||
return (
|
||||
re.search(r"(^|[,{]\s*)\+?ref\??\s*:", props_type_text, re.MULTILINE)
|
||||
is not None
|
||||
)
|
||||
|
||||
|
||||
def _append_ref_property_to_object_type(
|
||||
props_type_text: str, ref_prop_type: str
|
||||
) -> str:
|
||||
original = props_type_text
|
||||
stripped = original.rstrip()
|
||||
trailing_whitespace = original[len(stripped) :]
|
||||
|
||||
if stripped.startswith("{|") and stripped.endswith("|}"):
|
||||
open_token, close_token = "{|", "|}"
|
||||
elif stripped.startswith("{") and stripped.endswith("}"):
|
||||
open_token, close_token = "{", "}"
|
||||
else:
|
||||
return f"{{ ...{props_type_text.strip()}, +ref?: {ref_prop_type} }}"
|
||||
|
||||
inner = stripped[len(open_token) : -len(close_token)]
|
||||
if _props_text_has_ref_property(inner):
|
||||
return props_type_text
|
||||
|
||||
is_multiline = "\n" in stripped
|
||||
inner_content = inner.strip()
|
||||
if not is_multiline:
|
||||
if inner_content:
|
||||
separator = "," if not inner_content.endswith(",") else ""
|
||||
return (
|
||||
f"{open_token} {inner_content}{separator} +ref?: {ref_prop_type} "
|
||||
f"{close_token}{trailing_whitespace}"
|
||||
)
|
||||
return (
|
||||
f"{open_token} +ref?: {ref_prop_type} "
|
||||
f"{close_token}{trailing_whitespace}"
|
||||
)
|
||||
|
||||
# Multiline object type: place the new property on its own line and keep
|
||||
# existing indentation style if possible.
|
||||
closing_indent_match = re.search(r"\n([ \t]*)\s*$", inner)
|
||||
closing_indent = closing_indent_match.group(1) if closing_indent_match else ""
|
||||
property_indent_match = re.search(r"\n([ \t]+)\S", inner)
|
||||
property_indent = (
|
||||
property_indent_match.group(1)
|
||||
if property_indent_match
|
||||
else f"{closing_indent} "
|
||||
)
|
||||
|
||||
inner_without_trailing_space = inner.rstrip()
|
||||
if inner_content:
|
||||
if not inner_without_trailing_space.endswith(","):
|
||||
inner_without_trailing_space += ","
|
||||
if not inner_without_trailing_space.endswith("\n"):
|
||||
inner_without_trailing_space += "\n"
|
||||
new_inner = (
|
||||
f"{inner_without_trailing_space}"
|
||||
f"{property_indent}+ref?: {ref_prop_type},\n"
|
||||
f"{closing_indent}"
|
||||
)
|
||||
else:
|
||||
new_inner = (
|
||||
f"\n{property_indent}+ref?: {ref_prop_type},\n"
|
||||
f"{closing_indent}"
|
||||
)
|
||||
|
||||
return f"{open_token}{new_inner}{close_token}{trailing_whitespace}"
|
||||
|
||||
|
||||
def _with_optional_ref_property(
|
||||
props_type_text: str,
|
||||
props_type_node: Optional[Dict[str, Any]],
|
||||
instance_type_text: Optional[str],
|
||||
) -> str:
|
||||
if not instance_type_text:
|
||||
return props_type_text
|
||||
if _props_text_has_ref_property(props_type_text):
|
||||
return props_type_text
|
||||
ref_prop_type = _to_ref_setter_type(instance_type_text)
|
||||
if props_type_node and props_type_node.get("type") == "ObjectTypeAnnotation":
|
||||
if _object_type_has_ref_property(props_type_node):
|
||||
return props_type_text
|
||||
return _append_ref_property_to_object_type(props_type_text, ref_prop_type)
|
||||
if props_type_text.strip() == "any":
|
||||
return f"{{ +ref?: {ref_prop_type}, ... }}"
|
||||
return f"{{ ...{props_type_text}, +ref?: {ref_prop_type} }}"
|
||||
|
||||
|
||||
def _abstract_component_to_component_type(
|
||||
node: Dict[str, Any], content: bytes
|
||||
) -> Optional[str]:
|
||||
if node.get("type") != "GenericTypeAnnotation":
|
||||
return None
|
||||
if not _is_react_abstract_component_type(node.get("id")):
|
||||
return None
|
||||
|
||||
type_params = _type_parameter_nodes(node.get("typeParameters"))
|
||||
if not type_params:
|
||||
return "React.ComponentType<any>"
|
||||
|
||||
props_node = type_params[0]
|
||||
props_type = (
|
||||
_node_text(content, props_node).strip() if "range" in props_node else "any"
|
||||
)
|
||||
if not props_type:
|
||||
props_type = "any"
|
||||
|
||||
instance_type = None
|
||||
if len(type_params) >= 2:
|
||||
instance_node = type_params[1]
|
||||
if "range" in instance_node:
|
||||
instance_type = _node_text(content, instance_node).strip()
|
||||
|
||||
props_type = _with_optional_ref_property(props_type, props_node, instance_type)
|
||||
return f"React.ComponentType<{props_type}>"
|
||||
|
||||
|
||||
def _extract_ref_type_from_component(
|
||||
component_node: Dict[str, Any], content: bytes
|
||||
) -> Optional[str]:
|
||||
# Form: component(ref: Type, ...Props)
|
||||
for param in component_node.get("params", []):
|
||||
if not isinstance(param, dict):
|
||||
continue
|
||||
if param.get("name") == "ref":
|
||||
type_annotation = param.get("typeAnnotation")
|
||||
if isinstance(type_annotation, dict) and "range" in type_annotation:
|
||||
return _node_text(content, type_annotation).strip()
|
||||
|
||||
# Form: component(...{ ...Props, +ref?: Type })
|
||||
rest = component_node.get("rest")
|
||||
if not isinstance(rest, dict):
|
||||
return None
|
||||
rest_type = rest.get("typeAnnotation")
|
||||
if not isinstance(rest_type, dict):
|
||||
return None
|
||||
if rest_type.get("type") != "ObjectTypeAnnotation":
|
||||
return None
|
||||
for prop in rest_type.get("properties", []):
|
||||
if not isinstance(prop, dict):
|
||||
continue
|
||||
if prop.get("type") != "ObjectTypeProperty":
|
||||
continue
|
||||
if _object_property_key_name(prop) != "ref":
|
||||
continue
|
||||
value = prop.get("value")
|
||||
if isinstance(value, dict) and "range" in value:
|
||||
return _node_text(content, value).strip()
|
||||
return None
|
||||
|
||||
|
||||
def _component_to_legacy_type(
|
||||
component_node: Dict[str, Any], content: bytes
|
||||
) -> str:
|
||||
props_type = "any"
|
||||
props_type_node: Optional[Dict[str, Any]] = None
|
||||
rest = component_node.get("rest")
|
||||
if isinstance(rest, dict):
|
||||
rest_type = rest.get("typeAnnotation")
|
||||
if isinstance(rest_type, dict) and "range" in rest_type:
|
||||
props_type_node = rest_type
|
||||
extracted_props = _node_text(content, rest_type).strip()
|
||||
if extracted_props:
|
||||
props_type = extracted_props
|
||||
|
||||
ref_type = _extract_ref_type_from_component(component_node, content)
|
||||
|
||||
renders_type = None
|
||||
renders = component_node.get("rendersType")
|
||||
if isinstance(renders, dict):
|
||||
rendered = renders.get("typeAnnotation")
|
||||
if isinstance(rendered, dict) and "range" in rendered:
|
||||
renders_type = _node_text(content, rendered).strip()
|
||||
|
||||
ref_instance_type = ref_type
|
||||
if not ref_instance_type and renders_type and not _is_render_node_like(renders_type):
|
||||
# Non-React-node renders targets generally represent a component
|
||||
# interface/instance type. In modern Flow this is represented with a
|
||||
# `ref` prop typed with React.RefSetter<Instance>.
|
||||
ref_instance_type = renders_type
|
||||
|
||||
props_type = _with_optional_ref_property(
|
||||
props_type, props_type_node, ref_instance_type
|
||||
)
|
||||
|
||||
return f"React.ComponentType<{props_type}>"
|
||||
|
||||
|
||||
def _apply_replacements(
|
||||
content: bytes, replacements: List[Tuple[int, int, str]]
|
||||
) -> Tuple[bytes, int]:
|
||||
valid: List[Tuple[int, int, bytes]] = []
|
||||
for start, end, replacement in replacements:
|
||||
if start >= end:
|
||||
continue
|
||||
replacement_bytes = replacement.encode("utf-8")
|
||||
if content[start:end] == replacement_bytes:
|
||||
continue
|
||||
valid.append((start, end, replacement_bytes))
|
||||
|
||||
if not valid:
|
||||
return content, 0
|
||||
|
||||
# Prefer outer nodes first when ranges overlap.
|
||||
valid.sort(key=lambda item: (item[0], -(item[1] - item[0])))
|
||||
non_overlapping: List[Tuple[int, int, bytes]] = []
|
||||
current_end = -1
|
||||
for start, end, replacement_bytes in valid:
|
||||
if start < current_end:
|
||||
continue
|
||||
non_overlapping.append((start, end, replacement_bytes))
|
||||
current_end = end
|
||||
|
||||
updated = bytearray(content)
|
||||
for start, end, replacement_bytes in reversed(non_overlapping):
|
||||
updated[start:end] = replacement_bytes
|
||||
|
||||
return bytes(updated), len(non_overlapping)
|
||||
|
||||
|
||||
def _collect_component_and_renders_replacements(
|
||||
ast: Dict[str, Any], content: bytes
|
||||
) -> List[Tuple[int, int, str]]:
|
||||
replacements: List[Tuple[int, int, str]] = []
|
||||
|
||||
def visit(node: Dict[str, Any], parent_type: Optional[str]) -> None:
|
||||
node_type = node.get("type")
|
||||
if node_type == "ComponentTypeAnnotation" and "range" in node:
|
||||
start, end = node["range"]
|
||||
replacements.append((start, end, _component_to_legacy_type(node, content)))
|
||||
return
|
||||
|
||||
if node_type == "GenericTypeAnnotation" and "range" in node:
|
||||
replacement = _abstract_component_to_component_type(node, content)
|
||||
if replacement:
|
||||
start, end = node["range"]
|
||||
replacements.append((start, end, replacement))
|
||||
|
||||
if (
|
||||
node_type == "TypeOperator"
|
||||
and node.get("operator") == "renders"
|
||||
and parent_type != "ComponentTypeAnnotation"
|
||||
and "range" in node
|
||||
):
|
||||
type_annotation = node.get("typeAnnotation")
|
||||
if isinstance(type_annotation, dict) and "range" in type_annotation:
|
||||
rendered_type = _node_text(content, type_annotation)
|
||||
replacements.append(
|
||||
(
|
||||
node["range"][0],
|
||||
node["range"][1],
|
||||
_normalize_renders_target(rendered_type),
|
||||
)
|
||||
)
|
||||
|
||||
_walk(ast, visit)
|
||||
return replacements
|
||||
|
||||
|
||||
def _collect_as_replacements(
|
||||
ast: Dict[str, Any], content: bytes
|
||||
) -> List[Tuple[int, int, str]]:
|
||||
replacements: List[Tuple[int, int, str]] = []
|
||||
|
||||
def visit(node: Dict[str, Any], _parent_type: Optional[str]) -> None:
|
||||
if node.get("type") != "AsExpression":
|
||||
return
|
||||
if "range" not in node:
|
||||
return
|
||||
expression = node.get("expression")
|
||||
type_annotation = node.get("typeAnnotation")
|
||||
if not isinstance(expression, dict) or "range" not in expression:
|
||||
return
|
||||
if not isinstance(type_annotation, dict) or "range" not in type_annotation:
|
||||
return
|
||||
|
||||
start, end = node["range"]
|
||||
expr_start, expr_end = expression["range"]
|
||||
type_start, _type_end = type_annotation["range"]
|
||||
type_text = _node_text(content, type_annotation)
|
||||
|
||||
leading = content[start:expr_start].decode("utf-8")
|
||||
expr_core = content[expr_start:expr_end].decode("utf-8")
|
||||
between = content[expr_end:type_start].decode("utf-8")
|
||||
as_index = between.rfind(" as ")
|
||||
if as_index == -1:
|
||||
return
|
||||
|
||||
between_before_as = between[:as_index]
|
||||
trailing_comment = ""
|
||||
|
||||
line_comment_index = between_before_as.rfind("//")
|
||||
block_comment_index = between_before_as.rfind("/*")
|
||||
if line_comment_index != -1:
|
||||
trailing_comment = between_before_as[line_comment_index:].strip()
|
||||
between_before_as = between_before_as[:line_comment_index]
|
||||
elif block_comment_index != -1 and "*/" in between_before_as[block_comment_index:]:
|
||||
trailing_comment = between_before_as[block_comment_index:].strip()
|
||||
between_before_as = between_before_as[:block_comment_index]
|
||||
|
||||
expression_text = f"{leading}{expr_core}{between_before_as}".rstrip()
|
||||
replacement = f"({expression_text}: {type_text})"
|
||||
if trailing_comment:
|
||||
replacement = f"{replacement} {trailing_comment}"
|
||||
replacements.append((start, end, replacement))
|
||||
|
||||
_walk(ast, visit)
|
||||
return replacements
|
||||
|
||||
|
||||
def _remove_stale_prop_missing_fixmes(content: bytes) -> Tuple[bytes, int]:
|
||||
try:
|
||||
text = content.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return content, 0
|
||||
|
||||
lines = text.splitlines(keepends=True)
|
||||
keep_lines: List[str] = []
|
||||
removed = 0
|
||||
|
||||
specific_fixme_re = re.compile(r"^\s*// \$FlowFixMe\[prop-missing\]\s*$")
|
||||
any_fixme_re = re.compile(r"^\s*// \$FlowFixMe\[[^\]]+\]\s*$")
|
||||
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
current_stripped = lines[i].strip()
|
||||
if specific_fixme_re.match(current_stripped):
|
||||
j = i + 1
|
||||
inspected = 0
|
||||
remove_current = False
|
||||
while j < len(lines) and inspected < 8:
|
||||
candidate = lines[j].strip()
|
||||
inspected += 1
|
||||
if not candidate:
|
||||
j += 1
|
||||
continue
|
||||
if any_fixme_re.match(candidate):
|
||||
j += 1
|
||||
continue
|
||||
if "React.ComponentType<" in lines[j]:
|
||||
remove_current = True
|
||||
break
|
||||
if remove_current:
|
||||
removed += 1
|
||||
i += 1
|
||||
continue
|
||||
keep_lines.append(lines[i])
|
||||
i += 1
|
||||
|
||||
if removed == 0:
|
||||
return content, 0
|
||||
return "".join(keep_lines).encode("utf-8"), removed
|
||||
|
||||
|
||||
def _process_file(path: pathlib.Path) -> Tuple[bool, int]:
|
||||
original = path.read_bytes()
|
||||
|
||||
# Quick pre-filter to avoid expensive AST parsing on unrelated files.
|
||||
has_componenttype_prop_missing = (
|
||||
b"$FlowFixMe[prop-missing]" in original and b"React.ComponentType<" in original
|
||||
)
|
||||
if (
|
||||
b"component(" not in original
|
||||
and b"renders " not in original
|
||||
and b" as " not in original
|
||||
and b"AbstractComponent" not in original
|
||||
and b"React$AbstractComponent" not in original
|
||||
and not has_componenttype_prop_missing
|
||||
):
|
||||
return False, 0
|
||||
|
||||
try:
|
||||
text = original.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
return False, 0
|
||||
|
||||
relative_path = os.path.relpath(path, APP_DIR)
|
||||
ast = _run_flow_ast(relative_path, text)
|
||||
if ast is None:
|
||||
return False, 0
|
||||
|
||||
content = original
|
||||
total_replacements = 0
|
||||
|
||||
# Pass 1: component(...) + renders.
|
||||
pass1_replacements = _collect_component_and_renders_replacements(ast, content)
|
||||
content, applied = _apply_replacements(content, pass1_replacements)
|
||||
total_replacements += applied
|
||||
|
||||
# Pass 2: "as" casts. Run iteratively to handle nested casts.
|
||||
for _ in range(10):
|
||||
if b" as " not in content:
|
||||
break
|
||||
try:
|
||||
content_text = content.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
break
|
||||
as_ast = _run_flow_ast(relative_path, content_text)
|
||||
if as_ast is None:
|
||||
break
|
||||
as_replacements = _collect_as_replacements(as_ast, content)
|
||||
content, applied = _apply_replacements(content, as_replacements)
|
||||
total_replacements += applied
|
||||
if applied == 0:
|
||||
break
|
||||
|
||||
content, removed_fixmes = _remove_stale_prop_missing_fixmes(content)
|
||||
total_replacements += removed_fixmes
|
||||
|
||||
if content != original:
|
||||
path.write_bytes(content)
|
||||
return True, total_replacements
|
||||
return False, 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
files_modified = 0
|
||||
replacements_applied = 0
|
||||
|
||||
for path in sorted(SRC_DIR.rglob("*.js")):
|
||||
if not path.is_file():
|
||||
continue
|
||||
modified, replacements = _process_file(path)
|
||||
if modified:
|
||||
files_modified += 1
|
||||
replacements_applied += replacements
|
||||
|
||||
print(f" Modified {files_modified} files ({replacements_applied} replacements)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+884
@@ -0,0 +1,884 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Flow migration fix script (0.131 -> 0.299)
|
||||
# This script is idempotent - it can be run multiple times safely.
|
||||
# It fixes Flow type errors after upgrading from Flow 0.131 to 0.299.
|
||||
#
|
||||
# NOTE: We DO run "flow codemod annotate-exports", then normalize the
|
||||
# generated modern syntax (component()/renders/as-casts) back to
|
||||
# Babel-compatible Flow syntax.
|
||||
#
|
||||
# Usage: cd newIDE/app && bash scripts/fix-flow-errors.sh
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
APP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
export APP_DIR
|
||||
|
||||
cd "$APP_DIR"
|
||||
|
||||
echo "=== Flow Migration Fix Script ==="
|
||||
echo "Working directory: $APP_DIR"
|
||||
|
||||
###############################################################################
|
||||
# STEP 1: Ensure flow-libs directory exists
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 1: Ensuring flow-libs are in place ---"
|
||||
if [ ! -d "$APP_DIR/flow-libs" ]; then
|
||||
echo "ERROR: flow-libs directory not found."
|
||||
exit 1
|
||||
fi
|
||||
echo " OK."
|
||||
|
||||
###############################################################################
|
||||
# STEP 1b: Update flow-bin version to 0.299.0 in package.json and npm install
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 1b: Updating flow-bin version to 0.299.0 ---"
|
||||
sed -i 's/"flow-bin": "0.131.0"/"flow-bin": "0.299.0"/' "$APP_DIR/package.json"
|
||||
echo " Updated package.json. Running npm install..."
|
||||
npm install
|
||||
echo " Done."
|
||||
|
||||
###############################################################################
|
||||
# STEP 2: Update .flowconfig
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 2: Updating .flowconfig ---"
|
||||
|
||||
if ! grep -q 'flow-libs' "$APP_DIR/.flowconfig"; then
|
||||
sed -i '/^\[libs\]/a flow-libs' "$APP_DIR/.flowconfig"
|
||||
fi
|
||||
if ! grep -q '<PROJECT_ROOT>/flow-typed/\.\*' "$APP_DIR/.flowconfig"; then
|
||||
sed -i '/^\[declarations\]/a <PROJECT_ROOT>/flow-typed/.*' "$APP_DIR/.flowconfig"
|
||||
fi
|
||||
if ! grep -q 'GDevelop.js/types/\.\*' "$APP_DIR/.flowconfig"; then
|
||||
sed -i '/^\[declarations\]/a <PROJECT_ROOT>/../../GDevelop.js/types/.*' "$APP_DIR/.flowconfig"
|
||||
fi
|
||||
if ! grep -q '<PROJECT_ROOT>/flow-libs/\.\*' "$APP_DIR/.flowconfig"; then
|
||||
sed -i '/^\[declarations\]/a <PROJECT_ROOT>/flow-libs/.*' "$APP_DIR/.flowconfig"
|
||||
fi
|
||||
if ! grep -q 'fbjs' "$APP_DIR/.flowconfig"; then
|
||||
sed -i '/^\[declarations\]/a <PROJECT_ROOT>/node_modules/fbjs/.*' "$APP_DIR/.flowconfig"
|
||||
fi
|
||||
if ! grep -q '^\[lints\]' "$APP_DIR/.flowconfig"; then
|
||||
echo -e "\n[lints]" >> "$APP_DIR/.flowconfig"
|
||||
fi
|
||||
echo " Done."
|
||||
|
||||
###############################################################################
|
||||
# STEP 3: Fix deprecated syntax in src/ files
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 3: Fixing deprecated syntax ---"
|
||||
|
||||
# Fix existential type * -> any
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec perl -pi -e '
|
||||
s/(<[^>]*?)\*([,>])/${1}any${2}/g;
|
||||
s/(<[^>]*?)\*([,>])/${1}any${2}/g;
|
||||
s/& \*\b/\& any/g;
|
||||
' {} +
|
||||
|
||||
# Fix %checks (removed in new Flow)
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec sed -i 's/ %checks//g' {} +
|
||||
|
||||
# Fix React$ type names -> React. namespace
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec sed -i \
|
||||
-e 's/\bReact\$Element\b/React.Element/g' \
|
||||
-e 's/\bReact\$Component\b/React.Component/g' \
|
||||
-e 's/\bReact\$Node\b/React.Node/g' \
|
||||
-e 's/\bReact\$Context\b/React.Context/g' \
|
||||
-e 's/\bReact\$Ref\b/React.Ref/g' \
|
||||
-e 's/\bReact\$Key\b/React.Key/g' \
|
||||
-e 's/\bReact\$ElementRef\b/React.ElementRef/g' \
|
||||
-e 's/\bReact\$ElementConfig\b/React.ElementConfig/g' \
|
||||
-e 's/\bReact\$ComponentType\b/React.ComponentType/g' \
|
||||
{} +
|
||||
|
||||
# Fix $PropertyType -> indexed access type
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec perl -pi -e "
|
||||
s/\\\$PropertyType<([^,]+),\s*'([^']+)'>/\$1['\$2']/g;
|
||||
s/\\\$PropertyType<([^,]+),\s*\"([^\"]+)\">/\$1[\"\$2\"]/g;
|
||||
" {} +
|
||||
|
||||
# Fix $FlowExpectedError -> $FlowFixMe[incompatible-type]
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec perl -pi -e '
|
||||
s/\$FlowExpectedError(?!\[)/\$FlowFixMe[incompatible-type]/g;
|
||||
s/\$FlowExpectedError\[([^\]]+)\]/\$FlowFixMe[$1]/g;
|
||||
' {} +
|
||||
|
||||
# Fix old $FlowFixMe without error codes
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec perl -pi -e '
|
||||
s/\$FlowFixMe(?!\[)/\$FlowFixMe[incompatible-type]/g;
|
||||
' {} +
|
||||
|
||||
echo " Done."
|
||||
|
||||
###############################################################################
|
||||
# STEP 4: Run annotate-exports and normalize syntax
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 4: Running Flow codemod annotate-exports ---"
|
||||
|
||||
npx flow stop 2>/dev/null || true
|
||||
|
||||
for i in $(seq 1 5); do
|
||||
echo " Codemod iteration $i..."
|
||||
set +e
|
||||
CODEMOD_OUTPUT=$(npx flow codemod annotate-exports --write --default-any src 2>&1)
|
||||
CODEMOD_EXIT=$?
|
||||
set -e
|
||||
|
||||
echo "$CODEMOD_OUTPUT" | grep -E \
|
||||
"Files changed:|Number of annotations added:|Number of sig\. ver\. errors:|Number of annotations required:" || true
|
||||
|
||||
if [ $CODEMOD_EXIT -ne 0 ]; then
|
||||
echo " WARNING: annotate-exports exited with status $CODEMOD_EXIT"
|
||||
break
|
||||
fi
|
||||
|
||||
FILES_CHANGED=$(echo "$CODEMOD_OUTPUT" | sed -n 's/.*Files changed:[[:space:]]*\([0-9][0-9]*\).*/\1/p' | head -n 1)
|
||||
if [ -z "$FILES_CHANGED" ]; then
|
||||
echo " WARNING: Could not parse codemod output."
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$FILES_CHANGED" -eq 0 ]; then
|
||||
echo " Codemod converged."
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo " Normalizing codemod syntax to Babel-compatible Flow..."
|
||||
python3 "$SCRIPT_DIR/fix-codemod-syntax.py"
|
||||
|
||||
# Fix arrow-function inline object return types that prettier 1.15 can't parse.
|
||||
# The codemod may add return type annotations like:
|
||||
# const foo = (): { prop: Type } => { ... }
|
||||
# Prettier 1.15 can't parse the `: { ... } =>` pattern.
|
||||
# Fix: extract the inline type into a named type alias, preserving typing.
|
||||
echo " Fixing arrow-function return types for prettier compatibility..."
|
||||
python3 "$SCRIPT_DIR/fix-arrow-return-types.py"
|
||||
|
||||
echo " Done."
|
||||
|
||||
###############################################################################
|
||||
# STEP 4b: Fix codemod artifacts that cause parse errors or Flow issues
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 4b: Fixing codemod artifacts ---"
|
||||
|
||||
# 0. Remove stale FlowFixMe[prop-missing] comments that were previously added
|
||||
# for invalid React.AbstractComponent output from older codemod runs.
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec perl -0pi -e '
|
||||
s/^[ \t]*\/\/ \$FlowFixMe\[prop-missing\]\n(?=(?:[ \t]*\/\/ \$FlowFixMe\[[^\]]+\]\n|[ \t]*\n)*[^\n]*React\.ComponentType<)//mg;
|
||||
' {} +
|
||||
|
||||
# 1. Fix missing commas after ([]: Array<empty>) in object literals.
|
||||
# The codemod turns "[]," into "([]: Array<empty>)" but loses the comma.
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec perl -pi -e '
|
||||
s/\(\[\]: Array<empty>\)(\s*\/\/.*)$/([]: Array<empty>),$1/g;
|
||||
' {} +
|
||||
|
||||
# 2. Fix "new Array(n)" -> "new Array<number>(n)" to avoid underconstrained type
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec perl -pi -e '
|
||||
s/new Array\(([^)]+)\)\.fill\(0\)/new Array<number>($1).fill(0)/g;
|
||||
' {} +
|
||||
|
||||
# 3. Fix !== null comparisons on number types -> != null
|
||||
# (Flow 0.299 is strict about comparing non-nullable numbers to null)
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec perl -pi -e '
|
||||
s/selectedCompletionIndex !== null/selectedCompletionIndex != null/g;
|
||||
' {} +
|
||||
|
||||
# 5. Generic type params like new Set<any>(), new Map<any,any>(), React.createRef<any>()
|
||||
# are NOT parseable by prettier 1.15 (it treats < as comparison operator).
|
||||
# These will get FlowFixMe[underconstrained-implicit-instantiation] automatically.
|
||||
|
||||
# 6. new Array(n) patterns beyond .fill(0) also can't use <any> with prettier 1.15.
|
||||
|
||||
# 7. axios calls with type params - skip, prettier 1.15 mangles method<Type>() syntax.
|
||||
# These will get FlowFixMe[underconstrained-implicit-instantiation] automatically.
|
||||
|
||||
# 8. jest.fn() - leave as-is, prettier 1.15 can't parse jest.fn<any>()
|
||||
# These will get FlowFixMe[underconstrained-implicit-instantiation] automatically.
|
||||
|
||||
# 9. Fix method-unbinding for common patterns: .push.apply -> spread syntax
|
||||
# Handles both single-line and multi-line push.apply patterns.
|
||||
python3 << 'PYEOF'
|
||||
import os, re
|
||||
|
||||
src_dir = os.path.join(os.environ.get('APP_DIR', os.getcwd()), 'src')
|
||||
fixed = 0
|
||||
|
||||
for root, dirs, files in os.walk(src_dir):
|
||||
for fname in files:
|
||||
if not fname.endswith('.js'):
|
||||
continue
|
||||
filepath = os.path.join(root, fname)
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
original = content
|
||||
# Multi-line pattern: arr.push.apply(\n arr,\n items\n)
|
||||
content = re.sub(
|
||||
r'(\w+)\.push\.apply\(\s*\1\s*,\s*([^)]+?)\s*\)',
|
||||
lambda m: f'{m.group(1)}.push(...{m.group(2).strip()})',
|
||||
content,
|
||||
flags=re.DOTALL)
|
||||
if content != original:
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
fixed += 1
|
||||
|
||||
print(f" Converted {fixed} push.apply to spread syntax")
|
||||
PYEOF
|
||||
|
||||
# 10. Fix import-type-as-value: convert value imports of type-only symbols to import type.
|
||||
# I18n from @lingui/core is a type (interface) used only in type positions.
|
||||
find "$APP_DIR/src" -name "*.js" -type f -exec perl -pi -e '
|
||||
s/^(\/\/ \$FlowFixMe\[import-type-as-value\]\n)?import \{ I18n as I18nType \} from/import type { I18n as I18nType } from/g;
|
||||
' {} +
|
||||
|
||||
# Fix TreeViewItemContent import-type-as-value: these are interfaces (type-only)
|
||||
python3 << 'PYEOF'
|
||||
import os, re
|
||||
|
||||
app_dir = os.environ.get('APP_DIR', os.getcwd())
|
||||
src_dir = os.path.join(app_dir, 'src')
|
||||
fixed = 0
|
||||
|
||||
for root, dirs, files in os.walk(src_dir):
|
||||
for fname in files:
|
||||
if not fname.endswith('.js'):
|
||||
continue
|
||||
filepath = os.path.join(root, fname)
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
original = content
|
||||
|
||||
# Remove FlowFixMe[import-type-as-value] above import lines where
|
||||
# we can safely convert to import type
|
||||
lines = content.split('\n')
|
||||
new_lines = []
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
# Skip FlowFixMe[import-type-as-value] if the next line is an import we'll fix
|
||||
if '$FlowFixMe[import-type-as-value]' in line:
|
||||
# Check if this is a standalone FlowFixMe comment line
|
||||
stripped = line.lstrip()
|
||||
if stripped.startswith('// $FlowFixMe[import-type-as-value]'):
|
||||
# Check if next line is an import statement
|
||||
if i + 1 < len(lines) and 'import' in lines[i + 1]:
|
||||
# Skip this FlowFixMe comment
|
||||
i += 1
|
||||
continue
|
||||
# Check if this is inside an import block (destructured import)
|
||||
# Look backwards for the import statement
|
||||
is_in_import = False
|
||||
for j in range(i - 1, max(i - 10, -1), -1):
|
||||
if 'import' in lines[j] and '{' in lines[j]:
|
||||
is_in_import = True
|
||||
break
|
||||
if '}' in lines[j] and 'from' in lines[j]:
|
||||
break
|
||||
if is_in_import:
|
||||
# Skip this FlowFixMe
|
||||
i += 1
|
||||
continue
|
||||
new_lines.append(line)
|
||||
i += 1
|
||||
|
||||
content = '\n'.join(new_lines)
|
||||
|
||||
# Convert import { I18n as I18nType } to import type
|
||||
content = re.sub(
|
||||
r"import \{ I18n as I18nType \} from '@lingui/core'",
|
||||
"import type { I18n as I18nType } from '@lingui/core'",
|
||||
content)
|
||||
|
||||
if content != original:
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
fixed += 1
|
||||
|
||||
print(f" Fixed {fixed} import-type-as-value issues")
|
||||
PYEOF
|
||||
|
||||
# 4. Fix inline object return types on multi-line arrow function params
|
||||
# that fix-arrow-return-types.py couldn't handle (nested parens in destructured args).
|
||||
# Pattern: ): { ... } => { (where the object return type causes prettier parse error)
|
||||
python3 << 'PYEOF'
|
||||
import os, re, subprocess
|
||||
|
||||
app_dir = os.environ.get('APP_DIR', os.getcwd())
|
||||
|
||||
# Find files with prettier parse errors
|
||||
result = subprocess.run(
|
||||
['npx', 'prettier', '--list-different', 'src/!(locales)/**/*.js'],
|
||||
capture_output=True, text=True, cwd=app_dir, timeout=120
|
||||
)
|
||||
all_output = result.stdout + '\n' + result.stderr
|
||||
error_files = set()
|
||||
for line in all_output.split('\n'):
|
||||
m = re.match(r'\[error\]\s+(src/\S+\.js):\s+SyntaxError', line)
|
||||
if m:
|
||||
error_files.add(os.path.join(app_dir, m.group(1)))
|
||||
|
||||
fixed = 0
|
||||
for filepath in error_files:
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
original = content
|
||||
|
||||
# Find ): { ... } => patterns (multi-line object return types)
|
||||
# This regex works on the whole content to find the ): { ... } => pattern
|
||||
# We need to use a balanced-brace approach
|
||||
pos = 0
|
||||
replacements = []
|
||||
while True:
|
||||
idx = content.find('):', pos)
|
||||
if idx == -1:
|
||||
break
|
||||
# Check if followed by whitespace then {
|
||||
rest = content[idx+2:].lstrip()
|
||||
if not rest.startswith('{'):
|
||||
pos = idx + 2
|
||||
continue
|
||||
brace_start = content.index('{', idx + 2)
|
||||
# Find matching close brace
|
||||
depth = 0
|
||||
i = brace_start
|
||||
while i < len(content):
|
||||
if content[i] == '{':
|
||||
depth += 1
|
||||
elif content[i] == '}':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
break
|
||||
elif content[i] in ('"', "'", '`'):
|
||||
q = content[i]
|
||||
i += 1
|
||||
while i < len(content) and content[i] != q:
|
||||
if content[i] == '\\':
|
||||
i += 1
|
||||
i += 1
|
||||
i += 1
|
||||
if depth != 0:
|
||||
pos = idx + 2
|
||||
continue
|
||||
brace_end = i
|
||||
# Check if followed by => (arrow)
|
||||
after = content[brace_end+1:].lstrip()
|
||||
if not after.startswith('=>'):
|
||||
pos = idx + 2
|
||||
continue
|
||||
type_text = content[brace_start:brace_end+1]
|
||||
if '\n' not in type_text:
|
||||
pos = idx + 2
|
||||
continue
|
||||
# Find function name for type alias
|
||||
line_start = content.rfind('\n', 0, idx)
|
||||
if line_start == -1:
|
||||
line_start = 0
|
||||
# Find export const/let/var name = ... pattern
|
||||
preceding = content[max(0, line_start-200):idx+2]
|
||||
name_match = re.search(r'(?:const|let|var|function)\s+(\w+)', preceding)
|
||||
name = name_match.group(1) if name_match else 'Func'
|
||||
type_name = f'_{name[0].upper()}{name[1:]}ReturnType'
|
||||
# Check if type alias already exists
|
||||
if f'type {type_name}' in content:
|
||||
pos = idx + 2
|
||||
continue
|
||||
replacements.append((brace_start, brace_end+1, type_name, type_text))
|
||||
pos = brace_end + 1
|
||||
|
||||
if not replacements:
|
||||
continue
|
||||
|
||||
# Apply in reverse
|
||||
for brace_start, brace_end, type_name, type_text in reversed(replacements):
|
||||
content = content[:brace_start] + type_name + content[brace_end:]
|
||||
|
||||
# Insert type declarations at the top (after imports)
|
||||
insert_lines = []
|
||||
for _, _, type_name, type_text in replacements:
|
||||
insert_lines.append(f'type {type_name} = {type_text};\n')
|
||||
|
||||
# Find insertion point (after last import)
|
||||
last_import = 0
|
||||
for m in re.finditer(r'^(?:import\s|from\s)', content, re.MULTILINE):
|
||||
eol = content.find('\n', m.start())
|
||||
# Handle multi-line imports
|
||||
if '{' in content[m.start():eol] and '}' not in content[m.start():eol]:
|
||||
eol = content.find('}', eol)
|
||||
eol = content.find('\n', eol)
|
||||
# Handle from '...' on next line
|
||||
next_line_start = eol + 1
|
||||
if next_line_start < len(content):
|
||||
next_stripped = content[next_line_start:].lstrip()
|
||||
if next_stripped.startswith("from ") or next_stripped.startswith("} from "):
|
||||
eol = content.find('\n', next_line_start)
|
||||
if eol > last_import:
|
||||
last_import = eol
|
||||
if last_import > 0:
|
||||
insert_pos = last_import + 1
|
||||
else:
|
||||
insert_pos = 0
|
||||
insert_text = '\n' + ''.join(insert_lines) + '\n'
|
||||
content = content[:insert_pos] + insert_text + content[insert_pos:]
|
||||
|
||||
if content != original:
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
fixed += 1
|
||||
|
||||
print(f" Fixed {fixed} additional arrow-return-type files")
|
||||
PYEOF
|
||||
|
||||
echo " Done."
|
||||
|
||||
###############################################################################
|
||||
# STEP 5: Fix specific known issues
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 5: Fixing specific known issues ---"
|
||||
|
||||
# Fix GDevelop.js type files (if they exist and don't have FlowFixMe)
|
||||
GD_TYPES="$APP_DIR/../../GDevelop.js/types"
|
||||
if [ -d "$GD_TYPES" ]; then
|
||||
python3 << 'PYEOF'
|
||||
import os, re
|
||||
|
||||
gd_types = os.environ.get('GD_TYPES', '../../GDevelop.js/types')
|
||||
|
||||
for fname in ['gdbehaviorjsimplementation.js', 'gdbehaviorshareddatajsimplementation.js']:
|
||||
filepath = os.path.join(gd_types, fname)
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
if '$FlowFixMe' in content:
|
||||
continue
|
||||
for method in ['getProperties(', 'updateProperty(', 'initializeContent(']:
|
||||
content = content.replace(f' {method}', f' // $FlowFixMe[incompatible-type]\n {method}')
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
filepath = os.path.join(gd_types, 'libgdevelop.js')
|
||||
if os.path.isfile(filepath):
|
||||
with open(filepath) as f:
|
||||
lines = f.readlines()
|
||||
if '$FlowFixMe' not in ''.join(lines):
|
||||
fixes = {19: 'cannot-resolve-name', 20: 'cannot-resolve-name', 39: 'cannot-resolve-name', 297: 'cannot-resolve-name'}
|
||||
new_lines = []
|
||||
for i, line in enumerate(lines):
|
||||
if i + 1 in fixes:
|
||||
new_lines.append(f' // $FlowFixMe[{fixes[i+1]}]\n')
|
||||
new_lines.append(line)
|
||||
with open(filepath, 'w') as f:
|
||||
f.writelines(new_lines)
|
||||
|
||||
print(" Fixed GDevelop.js types")
|
||||
PYEOF
|
||||
fi
|
||||
|
||||
# Fix Theme/index.js - $PropertyType replacement may leave empty type or
|
||||
# indexed access type (Theme['gdevelopTheme']) that prettier 1.15 can't parse.
|
||||
# Use typeof DefaultLightTheme.gdevelopTheme which both Flow 0.299 and prettier understand.
|
||||
THEME_FILE="$APP_DIR/src/UI/Theme/index.js"
|
||||
python3 << 'PYEOF'
|
||||
import os, re
|
||||
theme_file = os.path.join(os.environ.get('APP_DIR', os.getcwd()), 'src/UI/Theme/index.js')
|
||||
if os.path.isfile(theme_file):
|
||||
with open(theme_file) as f:
|
||||
content = f.read()
|
||||
original = content
|
||||
# Fix empty type from $PropertyType removal
|
||||
content = content.replace("export type GDevelopTheme = ;",
|
||||
"export type GDevelopTheme = typeof DefaultLightTheme.gdevelopTheme;")
|
||||
# Fix indexed access type that prettier 1.15 can't parse
|
||||
content = re.sub(
|
||||
r"export type GDevelopTheme = Theme\['gdevelopTheme'\];",
|
||||
"export type GDevelopTheme = typeof DefaultLightTheme.gdevelopTheme;",
|
||||
content)
|
||||
# Remove any stray FlowFixMe above the type export
|
||||
content = re.sub(
|
||||
r'(?://\s*\$FlowFixMe\[incompatible-type\]\s*\n|\{/\*\s*\$FlowFixMe\[incompatible-type\]\s*\*/\}\s*\n)'
|
||||
r'(export type GDevelopTheme)',
|
||||
r'\1', content)
|
||||
# Remove any stale prettier-ignore before the line
|
||||
content = re.sub(
|
||||
r'// prettier-ignore\n(export type GDevelopTheme)',
|
||||
r'\1', content)
|
||||
if content != original:
|
||||
with open(theme_file, 'w') as f:
|
||||
f.write(content)
|
||||
print(" Fixed Theme/index.js")
|
||||
else:
|
||||
print(" Theme/index.js already correct")
|
||||
PYEOF
|
||||
|
||||
# Fix known annotate-exports gaps and formatting-displacement issues.
|
||||
python3 << 'PYEOF'
|
||||
import os
|
||||
import re
|
||||
|
||||
app_dir = os.environ.get('APP_DIR', os.getcwd())
|
||||
|
||||
# Regex-based replacements for signature-verification gaps
|
||||
replacements = [
|
||||
(
|
||||
os.path.join(app_dir, 'src/EventsSheet/index.js'),
|
||||
r'onResourceExternallyChanged = resourceInfo => \{',
|
||||
'onResourceExternallyChanged = (resourceInfo: any) => {',
|
||||
),
|
||||
(
|
||||
os.path.join(app_dir, 'src/Leaderboard/LeaderboardContext.js'),
|
||||
r'deleteLeaderboardEntry: async entryId => \{\},',
|
||||
'deleteLeaderboardEntry: async (entryId: any) => {},',
|
||||
),
|
||||
]
|
||||
|
||||
fixed = 0
|
||||
for filepath, pattern, replacement in replacements:
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
with open(filepath) as f:
|
||||
content = f.read()
|
||||
updated = re.sub(pattern, replacement, content)
|
||||
if updated != content:
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(updated)
|
||||
fixed += 1
|
||||
|
||||
# Fix PrivateGameTemplateStoreContext: add type annotation to empty object
|
||||
# that prettier splits across lines (causing FlowFixMe displacement).
|
||||
pgts_file = os.path.join(app_dir,
|
||||
'src/AssetStore/PrivateGameTemplates/PrivateGameTemplateStoreContext.js')
|
||||
if os.path.isfile(pgts_file):
|
||||
with open(pgts_file) as f:
|
||||
content = f.read()
|
||||
# Add type annotation to the untyped empty object
|
||||
updated = content.replace(
|
||||
'const privateGameTemplateListingDatasById = {};',
|
||||
'const privateGameTemplateListingDatasById: {[string]: any} = {};')
|
||||
# Remove any stacked FlowFixMe[prop-missing] that accumulated from previous runs
|
||||
updated = re.sub(
|
||||
r'(\s*// \$FlowFixMe\[prop-missing\]\n)+(\s*privateGameTemplateListingDatasById\[)',
|
||||
r'\2', updated)
|
||||
# Also remove FlowFixMe[invalid-computed-prop] for the typed object (no longer needed)
|
||||
updated = re.sub(
|
||||
r'\s*// \$FlowFixMe\[invalid-computed-prop\]\n(\s*if \(privateGameTemplateListingDatasById\[)',
|
||||
r'\n\1', updated)
|
||||
if updated != content:
|
||||
with open(pgts_file, 'w') as f:
|
||||
f.write(updated)
|
||||
fixed += 1
|
||||
|
||||
# Fix ConditionsActionsColumns.js - renderActionsList type needs style prop
|
||||
cac_file = os.path.join(app_dir, 'src/EventsSheet/EventsTree/ConditionsActionsColumns.js')
|
||||
if os.path.isfile(cac_file):
|
||||
with open(cac_file) as f:
|
||||
content = f.read()
|
||||
updated = content.replace(
|
||||
'renderActionsList: ({ className: string }) => React.Node,',
|
||||
'renderActionsList: ({ style?: Object, className: string }) => React.Node,')
|
||||
if updated != content:
|
||||
with open(cac_file, 'w') as f:
|
||||
f.write(updated)
|
||||
fixed += 1
|
||||
|
||||
# Fix PreferencesContext.js - annotate missing params for signature-verification
|
||||
pref_file = os.path.join(app_dir, 'src/MainFrame/Preferences/PreferencesContext.js')
|
||||
if os.path.isfile(pref_file):
|
||||
with open(pref_file) as f:
|
||||
content = f.read()
|
||||
updated = content
|
||||
updated = updated.replace(
|
||||
'getRecentProjectFiles: options => [],',
|
||||
'getRecentProjectFiles: (options: any): any => [],')
|
||||
updated = updated.replace(
|
||||
'getEditorStateForProject: projectId => {},',
|
||||
'getEditorStateForProject: (projectId: any): any => {},')
|
||||
if updated != content:
|
||||
with open(pref_file, 'w') as f:
|
||||
f.write(updated)
|
||||
fixed += 1
|
||||
|
||||
# Fix ShortcutsList.js - annotate commandName parameter
|
||||
sc_file = os.path.join(app_dir, 'src/KeyboardShortcuts/ShortcutsList.js')
|
||||
if os.path.isfile(sc_file):
|
||||
with open(sc_file) as f:
|
||||
content = f.read()
|
||||
updated = content.replace(
|
||||
'areaWiseCommands[areaName].map(commandName =>',
|
||||
'areaWiseCommands[areaName].map((commandName: string) =>')
|
||||
if updated != content:
|
||||
with open(sc_file, 'w') as f:
|
||||
f.write(updated)
|
||||
fixed += 1
|
||||
|
||||
# Fix ProjectManager/index.js - useState(null) needs type param for ?gdLayout
|
||||
pm_file = os.path.join(app_dir, 'src/ProjectManager/index.js')
|
||||
if os.path.isfile(pm_file):
|
||||
with open(pm_file) as f:
|
||||
content = f.read()
|
||||
updated = content
|
||||
# Add type annotation to useState calls that should be ?gdLayout
|
||||
updated = re.sub(
|
||||
r'const \[editedPropertiesLayout, setEditedPropertiesLayout\] = React\.useState\(\s*\n\s*null\s*\n\s*\)',
|
||||
'const [editedPropertiesLayout, setEditedPropertiesLayout] = React.useState<?gdLayout>(\n null\n )',
|
||||
updated)
|
||||
updated = re.sub(
|
||||
r'const \[editedVariablesLayout, setEditedVariablesLayout\] = React\.useState\(\s*\n\s*null\s*\n\s*\)',
|
||||
'const [editedVariablesLayout, setEditedVariablesLayout] = React.useState<?gdLayout>(\n null\n )',
|
||||
updated)
|
||||
if updated != content:
|
||||
with open(pm_file, 'w') as f:
|
||||
f.write(updated)
|
||||
fixed += 1
|
||||
|
||||
# Fix CollisionMasksPreview.js - use type cast for inexact/exact mismatch
|
||||
cmp_file = os.path.join(app_dir, 'src/ObjectEditor/Editors/SpriteEditor/CollisionMasksEditor/CollisionMasksPreview.js')
|
||||
if os.path.isfile(cmp_file):
|
||||
with open(cmp_file) as f:
|
||||
content = f.read()
|
||||
# Cast polygons to any to bypass inexact/exact mismatch
|
||||
updated = content.replace(
|
||||
'mapVector(polygons,',
|
||||
'mapVector((polygons: any),')
|
||||
if updated != content:
|
||||
with open(cmp_file, 'w') as f:
|
||||
f.write(updated)
|
||||
fixed += 1
|
||||
|
||||
print(f" Fixed {fixed} known annotate-exports signature gaps and type issues")
|
||||
PYEOF
|
||||
|
||||
echo " Done."
|
||||
|
||||
###############################################################################
|
||||
# STEP 6: Run prettier formatting (pre-FlowFixMe)
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 6: Running prettier formatting (pre-FlowFixMe) ---"
|
||||
|
||||
# Format all source files before adding FlowFixMe.
|
||||
# This ensures FlowFixMe comments will be placed at the correct post-format
|
||||
# line numbers.
|
||||
npm run format 2>&1 || true
|
||||
echo " Done."
|
||||
|
||||
###############################################################################
|
||||
# STEP 7: Add $FlowFixMe for all remaining errors (iterative)
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 7: Adding \$FlowFixMe for all errors (iterative) ---"
|
||||
|
||||
npx flow stop 2>/dev/null || true
|
||||
|
||||
PREV_ERROR_COUNT=""
|
||||
for i in $(seq 1 15); do
|
||||
echo " Iteration $i..."
|
||||
|
||||
# Add FlowFixMe comments for errors
|
||||
FLOWFIXME_OUTPUT=$(python3 "$SCRIPT_DIR/add-flow-fixme.py" 2>&1 || true)
|
||||
echo "$FLOWFIXME_OUTPUT" | grep -E "Added|Found|No" || true
|
||||
|
||||
# Convert // FlowFixMe in JSX context to {/* FlowFixMe */}
|
||||
JSX_CONVERT_OUTPUT=$(python3 "$SCRIPT_DIR/convert-jsx-flowfixme.py" 2>&1 || true)
|
||||
echo "$JSX_CONVERT_OUTPUT" | grep -E "Converted|Reverted" || true
|
||||
|
||||
npx flow stop 2>/dev/null || true
|
||||
|
||||
# Check remaining errors
|
||||
set +e
|
||||
ERROR_OUTPUT=$(npx flow 2>&1)
|
||||
FLOW_EXIT=$?
|
||||
set -e
|
||||
if echo "$ERROR_OUTPUT" | grep -q "No errors"; then
|
||||
echo " No more errors!"
|
||||
break
|
||||
fi
|
||||
|
||||
ERROR_COUNT=$(echo "$ERROR_OUTPUT" | grep "Found" | grep -o '[0-9]*' | head -1)
|
||||
echo " Remaining errors: ${ERROR_COUNT:-unknown} (flow exit: $FLOW_EXIT)"
|
||||
|
||||
if echo "$FLOWFIXME_OUTPUT" | grep -q "Added 0 \$FlowFixMe comments" && \
|
||||
[ -n "$ERROR_COUNT" ] && [ "$ERROR_COUNT" = "$PREV_ERROR_COUNT" ]; then
|
||||
echo " No automatic progress in this iteration; stopping early."
|
||||
break
|
||||
fi
|
||||
|
||||
PREV_ERROR_COUNT="$ERROR_COUNT"
|
||||
npx flow stop 2>/dev/null || true
|
||||
done
|
||||
|
||||
###############################################################################
|
||||
# STEP 8: Post-FlowFixMe format + fix cycle
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 8: Post-FlowFixMe format and fix cycle ---"
|
||||
|
||||
# After adding FlowFixMe comments, formatting may introduce new errors
|
||||
# (e.g., FlowFixMe comments getting displaced from error lines).
|
||||
# Run format + flow check + FlowFixMe in a cycle until stable.
|
||||
for j in $(seq 1 3); do
|
||||
echo " Format-fix cycle iteration $j..."
|
||||
|
||||
npm run format 2>&1 || true
|
||||
|
||||
npx flow stop 2>/dev/null || true
|
||||
set +e
|
||||
CYCLE_CHECK=$(npx flow 2>&1)
|
||||
set -e
|
||||
|
||||
if echo "$CYCLE_CHECK" | grep -q "No errors"; then
|
||||
echo " No errors after formatting - cycle complete!"
|
||||
break
|
||||
fi
|
||||
|
||||
CYCLE_ERRORS=$(echo "$CYCLE_CHECK" | grep "Found" | grep -o '[0-9]*' | head -1)
|
||||
echo " $CYCLE_ERRORS errors after formatting, adding FlowFixMe..."
|
||||
|
||||
python3 "$SCRIPT_DIR/add-flow-fixme.py" 2>&1 || true
|
||||
python3 "$SCRIPT_DIR/convert-jsx-flowfixme.py" 2>&1 || true
|
||||
done
|
||||
|
||||
echo " Done."
|
||||
|
||||
###############################################################################
|
||||
# STEP 9: Final format verification
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 9: Final format verification ---"
|
||||
|
||||
npm run format 2>&1 || true
|
||||
|
||||
npx flow stop 2>/dev/null || true
|
||||
set +e
|
||||
FINAL_CHECK=$(npx flow 2>&1)
|
||||
set -e
|
||||
|
||||
if echo "$FINAL_CHECK" | grep -q "No errors"; then
|
||||
echo " All clean!"
|
||||
else
|
||||
echo " WARNING: Still have errors after all cycles:"
|
||||
echo "$FINAL_CHECK" | grep "Found" || true
|
||||
fi
|
||||
|
||||
echo " Done."
|
||||
|
||||
###############################################################################
|
||||
# STEP 10: Fix eslint-disable-next-line ordering with $FlowFixMe
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 10: Fixing eslint/FlowFixMe comment ordering ---"
|
||||
|
||||
# When $FlowFixMe is inserted between eslint-disable-next-line and the code,
|
||||
# eslint-disable no longer applies (it targets the FlowFixMe comment instead).
|
||||
# Fix: wrap the code with eslint-disable/eslint-enable block comments, so that
|
||||
# both the FlowFixMe suppression and the eslint suppression work correctly.
|
||||
python3 << 'PYEOF'
|
||||
import os, re
|
||||
|
||||
src_dir = os.path.join(os.environ.get('APP_DIR', os.getcwd()), 'src')
|
||||
fixed = 0
|
||||
|
||||
for root, dirs, files in os.walk(src_dir):
|
||||
for fname in files:
|
||||
if not fname.endswith('.js'):
|
||||
continue
|
||||
filepath = os.path.join(root, fname)
|
||||
with open(filepath) as f:
|
||||
lines = f.readlines()
|
||||
|
||||
modified = False
|
||||
new_lines = []
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
# Pattern: eslint-disable-next-line, then one or more $FlowFixMe[...], then code
|
||||
if (i + 2 < len(lines) and
|
||||
'eslint-disable-next-line' in lines[i] and
|
||||
'$FlowFixMe[' in lines[i + 1]):
|
||||
m = re.search(r'eslint-disable-next-line\s+(.+)', lines[i])
|
||||
if m:
|
||||
rule = m.group(1).strip()
|
||||
# Collect all consecutive FlowFixMe lines
|
||||
fixme_lines = []
|
||||
j = i + 1
|
||||
while j < len(lines) and '$FlowFixMe[' in lines[j]:
|
||||
fixme_lines.append(lines[j])
|
||||
j += 1
|
||||
if j < len(lines):
|
||||
code_line = lines[j]
|
||||
indent = ''
|
||||
for ch in code_line:
|
||||
if ch in (' ', '\t'):
|
||||
indent += ch
|
||||
else:
|
||||
break
|
||||
# Use eslint-disable/enable block wrapping FlowFixMe + code
|
||||
new_lines.append(f'{indent}/* eslint-disable {rule} */\n')
|
||||
for fl in fixme_lines:
|
||||
new_lines.append(fl)
|
||||
new_lines.append(code_line)
|
||||
new_lines.append(f'{indent}/* eslint-enable {rule} */\n')
|
||||
i = j + 1
|
||||
modified = True
|
||||
fixed += 1
|
||||
continue
|
||||
# Pattern: one or more $FlowFixMe[...], then eslint-disable-next-line, then code
|
||||
elif (i + 2 < len(lines) and
|
||||
'$FlowFixMe[' in lines[i] and
|
||||
'eslint-disable-next-line' in lines[i + 1]):
|
||||
# Find where the FlowFixMe block starts
|
||||
fixme_start = i
|
||||
# The eslint line is after one FlowFixMe; collect FlowFixMe after it too
|
||||
m = re.search(r'eslint-disable-next-line\s+(.+)', lines[i + 1])
|
||||
if m:
|
||||
rule = m.group(1).strip()
|
||||
j = i + 2
|
||||
# Code line is right after eslint-disable-next-line
|
||||
if j < len(lines):
|
||||
code_line = lines[j]
|
||||
indent = ''
|
||||
for ch in code_line:
|
||||
if ch in (' ', '\t'):
|
||||
indent += ch
|
||||
else:
|
||||
break
|
||||
new_lines.append(f'{indent}/* eslint-disable {rule} */\n')
|
||||
new_lines.append(lines[i]) # Keep $FlowFixMe
|
||||
new_lines.append(code_line)
|
||||
new_lines.append(f'{indent}/* eslint-enable {rule} */\n')
|
||||
i = j + 1
|
||||
modified = True
|
||||
fixed += 1
|
||||
continue
|
||||
|
||||
new_lines.append(lines[i])
|
||||
i += 1
|
||||
|
||||
if modified:
|
||||
with open(filepath, 'w') as f:
|
||||
f.writelines(new_lines)
|
||||
|
||||
print(f" Fixed {fixed} eslint/FlowFixMe orderings")
|
||||
PYEOF
|
||||
|
||||
###############################################################################
|
||||
# STEP 11: Final format after all fixes
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "--- Step 11: Final format pass ---"
|
||||
npm run format 2>&1 || true
|
||||
echo " Done."
|
||||
|
||||
echo ""
|
||||
echo "=== Flow Migration Fix Script Complete ==="
|
||||
echo "Run 'npm run flow' and 'npm run build' to verify."
|
||||
Reference in New Issue
Block a user