Skip to content
This repository has been archived by the owner on Mar 5, 2023. It is now read-only.

Rewrite to ts morph #78

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bae5ac4
Rewrite for type-level extractor using ts-morph
Andarist Sep 14, 2020
1805f5b
Merge branch 'master' into rewrite-to-ts-morph
Andarist Sep 14, 2020
9b45dfc
Implement array extraction
Andarist Sep 14, 2020
560fe30
Fixed minor issues
Andarist Sep 15, 2020
748d65e
Implemented options extractor
Andarist Sep 15, 2020
2564bd3
Added support for extracting delayed transitions
Andarist Sep 15, 2020
88f143d
Merge branch 'master' into rewrite-to-ts-morph
Andarist Sep 15, 2020
cc61a46
Add support for extracting `always`
Andarist Sep 16, 2020
0cf8405
Use entry/exit properties over onEntry/onExit in the complex machine …
Andarist Sep 16, 2020
c534077
Added support for action objects and fixed issue with inline function…
Andarist Sep 16, 2020
4b4f3f4
Fixed the Action extractor when there is no type for it and add funct…
Andarist Sep 16, 2020
3d3696c
Allow for inline actions in the Actions extractor
Andarist Sep 16, 2020
218b5a4
Rewritten to extract based on the inline AST nodes and only fallback …
Andarist Sep 19, 2020
1fa58b1
Merge branch 'master' into rewrite-to-ts-morph
Andarist Sep 20, 2020
61783fa
Rewrite extraction to work purely~ on types
Andarist Feb 25, 2021
06d6dd6
Remove undefined initial from one of the parallel examples
Andarist Apr 9, 2021
df7f672
Fixed an issue in the Action extractor
Andarist Apr 9, 2021
683b6ec
Merge branch 'master' into rewrite-to-ts-morph
Andarist Apr 9, 2021
cbade56
Switch to using createMachine exclusively and fix some tests which we…
Andarist Apr 9, 2021
9a7c2d1
Add delays extractor to the options extractor
Andarist Apr 9, 2021
ea38207
Added fail cases
mattpocock Apr 13, 2021
d98aac0
Fixed extracting string actions from onDone
Andarist Apr 17, 2021
44aec32
Add snapshots releases workflow
Andarist Apr 29, 2021
0b74547
Add changeset
Andarist Apr 29, 2021
b4f1697
Update Changesets
Andarist Apr 29, 2021
0305fc1
Tweak snapshot release workflow
Andarist Apr 29, 2021
a140c23
Empty commit
Andarist Apr 29, 2021
d352d52
Remove publish-related scripts from the xstate-compiled directory
Andarist Apr 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Rewrite for type-level extractor using ts-morph
Andarist committed Sep 14, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit bae5ac4b094148bb6515bfe694176446e623830d
4 changes: 3 additions & 1 deletion packages/xstate-compiled/examples/fetchMachine.machine.ts
Original file line number Diff line number Diff line change
@@ -28,7 +28,9 @@ const machine = Machine<Context, Event, 'fetchMachine'>({
},
},
success: {
entry: ['celebrate'],
// TODO: need to implement arrays support properly
// entry: ['celebrate'],
entry: 'celebrate',
},
},
});
7 changes: 1 addition & 6 deletions packages/xstate-compiled/package.json
Original file line number Diff line number Diff line change
@@ -6,21 +6,16 @@
"license": "MIT",
"repository": "https://github.com/mattpocock/xstate-codegen",
"dependencies": {
"@babel/core": "^7.10.4",
"@babel/helper-split-export-declaration": "^7.11.0",
"@babel/parser": "^7.10.4",
"@babel/plugin-proposal-optional-chaining": "^7.10.4",
"@babel/plugin-transform-typescript": "^7.10.4",
"@rollup/plugin-babel": "^5.2.0",
"@rollup/plugin-node-resolve": "^9.0.0",
"babel-plugin-macros": "^2.8.0",
"colors": "^1.4.0",
"gaze": "^1.1.3",
"glob": "^7.1.6",
"handlebars": "^4.7.6",
"handlebars-helpers": "^0.10.0",
"pkg-up": "^3.1.0",
"rollup": "^2.26.3",
"ts-morph": "^8.1.0",
"xstate": "^4.12.0"
},
"devDependencies": {
223 changes: 223 additions & 0 deletions packages/xstate-compiled/src/configExtractor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import { Type, ts } from 'ts-morph';

const indexer = Symbol('schema.extractor.indexer');

// TODO: implement support for inline functions - we just need to skip them
// but probably would be good to declare that in a schema somehow?

type TypeExtractor = {
extract: (
type: Type | undefined,
) => [true, undefined] | [false, any, boolean?];
};

const lazy = (getter: () => TypeExtractor): TypeExtractor => ({
extract: (type: Type | undefined) => getter().extract(type),
});
const object = (
shape: Record<string | symbol, TypeExtractor>,
): TypeExtractor => ({
extract(type: Type | undefined) {
if (!type || !type.isObject()) {
return [true, undefined];
}

const objectType = type as Type<ts.ObjectType>;

if (objectType.getStringIndexType() || objectType.getNumberIndexType()) {
// we don't allow indexer types, we need to resolve to literal keys
return [true, undefined];
}

const extracted: any = {};

for (const key of Object.keys(shape)) {
const valueDeclar = objectType
.getProperty(key)
?.getValueDeclarationOrThrow() as any; /* PropertyAssignment */
const propType = valueDeclar?.getInitializerOrThrow().getType();

const [err, value, hasValue] = shape[key].extract(propType);
if (err) {
return [err, undefined];
}
if (hasValue) {
extracted[key] = value;
}
}

if (shape[indexer as any]) {
const indexerExtractor: TypeExtractor = shape[indexer as any];
for (const prop of objectType.getProperties()) {
const name = prop.getName();
if (name in shape) {
continue;
}
const valueDeclar = prop?.getValueDeclarationOrThrow() as any; /* PropertyAssignment */
const propType = valueDeclar?.getInitializerOrThrow().getType();

const [err, value, hasValue] = indexerExtractor.extract(propType);
if (err) {
return [err, undefined];
}
if (hasValue) {
extracted[name] = value;
}
}
}

return [false, extracted, true];
},
});
const optional = (t: TypeExtractor): TypeExtractor => ({
extract(type: Type | undefined) {
if (!type) {
return [false, undefined, false];
}
return t.extract(type);
},
});
const array = (type: TypeExtractor): TypeExtractor => ({
extract(type: Type | undefined) {
if (!type || !type.isArray()) {
return [true, undefined];
}
throw new Error('Extracting arrays is not implemented yet.');
},
});
const match = (candidates: TypeExtractor[]): TypeExtractor => ({
extract(type: Type | undefined) {
for (const candidate of candidates) {
const [, value, hasValue] = candidate.extract(type);
if (hasValue) {
return [false, value, true];
}
}

return [true, undefined];
},
});
const undef = (): TypeExtractor => ({
extract(type: Type | undefined) {
if (!type || !type.isUndefined()) {
return [true, undefined];
}
return [false, undefined, true];
},
});
const bool = (literal?: boolean): TypeExtractor => ({
extract(type: Type | undefined) {
if (!type || !type.isBooleanLiteral()) {
return [true, undefined];
}
return [false, (type.compilerType as any).intrinsicName === 'true', true];
},
});
const string = (literals?: string[]): TypeExtractor => ({
extract(type: Type | undefined) {
if (!type || !type.isStringLiteral()) {
return [true, undefined];
}

const literal = (type.compilerType as any).value;
if (!literals || literals.includes(literal)) {
return [false, literal, true];
}
return [true, undefined];
},
});

const SingleOrArray = (type: TypeExtractor): TypeExtractor =>
match([type, array(type)]);

const Actions = SingleOrArray(string());

const Target = match([undef(), SingleOrArray(string())]);

const Transition = match([
Target,
object({
target: Target,
cond: optional(string()),
actions: optional(Actions),
internal: optional(bool()),
}),
]);

const TransitionsMap = object({
[indexer]: SingleOrArray(Transition),
});

const Invoke = SingleOrArray(
object({
// TODO: this can be an object with .type
src: string(),
id: optional(string()),
onDone: optional(SingleOrArray(Transition)),
onError: optional(SingleOrArray(Transition)),
autoForward: optional(bool()),
// TODO:
// data:
}),
);

const AtomicState = object({
type: optional(string(['atomic'])),
id: optional(string()),
entry: optional(Actions),
exit: optional(Actions),
invoke: optional(Invoke),
on: optional(TransitionsMap),
});
const CompoundState = object({
type: optional(string(['compound'])),
id: optional(string()),
initial: string(),
entry: optional(Actions),
exit: optional(Actions),
invoke: optional(Invoke),
states: lazy(() => States),
on: optional(TransitionsMap),
});
const ParallelState = object({
type: string(['parallel']),
id: optional(string()),
entry: optional(Actions),
exit: optional(Actions),
invoke: optional(Invoke),
states: lazy(() => States),
on: optional(TransitionsMap),
});
const FinalState = object({
type: string(['final']),
id: optional(string()),
entry: optional(Actions),
exit: optional(Actions),
// TODO: implement it
// data: ?
});
const HistoryState = object({
type: string(['history']),
history: match([string(['shallow', 'deep']), bool(true)]),
// XState seems to allow undefined here, that's weird? what would it mean?
// it also only allows StateValue, need to recheck how the whole thing behaves
// let's keep this defined as a simple string for now
target: string(),
});

// order matters here - compound and atomic have to come last, in that order
const State = match([
FinalState,
ParallelState,
HistoryState,
CompoundState,
AtomicState,
]);

const States = object({
[indexer]: State,
});

const extractConfig = (configType: Type) => State.extract(configType);

export default extractConfig;
Loading