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

Experiments with cross-module parsing #11

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/__tests__/importsAndExports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { parseMachinesFromFile } from "..";

describe("Imports and exports", () => {
it("Should pick up an identifier exported from a separate file", () => {
const file1 = `
import { options } from './options';

createMachine({}, options);
`;

const optionsFile = `
export const options = {
actions: {
nice: () => {},
}
}
`;

const result = parseMachinesFromFile(file1, {
resolveRequire: () => optionsFile,
});

expect(result.machines[0].ast?.options).toBeTruthy();
});
});
34 changes: 34 additions & 0 deletions src/identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,40 @@ export const identifierReferencingVariableDeclaration = <Result>(
});
};

/**
* Finds a declarator in the same file which corresponds
* to an identifier of the name you provide
*/
export const findImportWithName = (
file: any,
name: string,
): t.VariableDeclarator | null | undefined => {
let declarator: t.VariableDeclarator | null | undefined = null;

traverse(file, {
VariableDeclarator(path) {
if (t.isIdentifier(path.node.id) && path.node.id.name === name) {
declarator = path.node as any;
}
},
});

return declarator;
};

export const identifierReferencingImport = <Result>(
parser: AnyParser<Result>,
) => {
return createParser({
babelMatcher: t.isIdentifier,
parseNode: (node, context) => {
const variableDeclarator = findImportWithName(context.file, node.name);

return parser.parse(variableDeclarator?.init, context);
},
});
};

export const maybeIdentifierTo = <Result>(parser: AnyParser<Result>) => {
return unionType([parser, identifierReferencingVariableDeclaration(parser)]);
};
8 changes: 7 additions & 1 deletion src/parseMachinesFromFile.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
import { MachineConfig } from "xstate";
import { ParseOptions } from ".";
import { MachineCallExpression } from "./machineCallExpression";
import { MachineParseResult } from "./MachineParseResult";
import { toMachineConfig } from "./toMachineConfig";
import { ParseResult } from "./types";

export const parseMachinesFromFile = (fileContents: string): ParseResult => {
export const parseMachinesFromFile = (
fileContents: string,
options?: ParseOptions,
): ParseResult => {
if (
!fileContents.includes("createMachine") &&
!fileContents.includes("Machine")
Expand All @@ -23,6 +27,7 @@ export const parseMachinesFromFile = (fileContents: string): ParseResult => {
"jsx",
["decorators", { decoratorsBeforeExport: false }],
],
sourceFilename: options?.sourceFilename,
});

let result: ParseResult = {
Expand All @@ -33,6 +38,7 @@ export const parseMachinesFromFile = (fileContents: string): ParseResult => {
CallExpression(path: any) {
const ast = MachineCallExpression.parse(path.node, {
file: parseResult,
resolveRequire: options?.resolveRequire || (() => undefined),
});
if (ast) {
result.machines.push(new MachineParseResult({ ast }));
Expand Down
12 changes: 12 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export interface StringLiteralNode {

export interface ParserContext {
file: t.File;
resolveRequire: (
sourceFile: string,
relativeImport: string,
) => string | undefined;
}

export interface Parser<T extends t.Node = any, Result = any> {
Expand All @@ -26,3 +30,11 @@ export interface AnyParser<Result> {
export interface ParseResult {
machines: MachineParseResult[];
}

export interface ParseOptions {
sourceFilename?: string;
resolveRequire?: (
sourceFile: string,
relativeImport: string,
) => string | undefined;
}