Skip to content

Commit

Permalink
feat: add support for TSX and JSX files
Browse files Browse the repository at this point in the history
  • Loading branch information
antoine-coulon committed Oct 21, 2022
1 parent 885d69a commit eb69c51
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 5 deletions.
Expand Up @@ -11,7 +11,9 @@ export class JavaScriptModuleWalker implements ModuleWalker {
const moduleDeclarations = new Set<string>();
const node = parseScript(fileContent, {
module: true,
next: true
next: true,
jsx: true,
loc: false
});
const isRootNode = node.type === "Program";

Expand Down
13 changes: 12 additions & 1 deletion packages/skott/src/modules/walkers/ecmascript/module-resolver.ts
Expand Up @@ -42,7 +42,18 @@ export function isBinaryModule(module: string): boolean {
return module.endsWith(".node");
}

const kExpectedModuleExtensions = new Set([".js", ".mjs", ".cjs", ".ts"]);
export function isTypeScriptModule(module: string): boolean {
return path.extname(module) === ".ts" || path.extname(module) === ".tsx";
}

const kExpectedModuleExtensions = new Set([
".js",
".jsx",
".mjs",
".cjs",
".ts",
".tsx"
]);

export function isSupportedModule(module: string): boolean {
return kExpectedModuleExtensions.has(path.extname(module));
Expand Down
Expand Up @@ -13,7 +13,7 @@ export class TypeScriptModuleWalker implements ModuleWalker {
public async walk(fileContent: string): Promise<ModuleWalkerResult> {
const { parse } = await import("@typescript-eslint/typescript-estree");
const moduleDeclarations = new Set<string>();
const node = parse(fileContent);
const node = parse(fileContent, { jsx: true, loc: false, comment: false });
const isRootNode = node.type === "Program";

walk(isRootNode ? node.body : node, {
Expand Down
5 changes: 3 additions & 2 deletions packages/skott/src/skott.ts
Expand Up @@ -12,7 +12,8 @@ import {
isBinaryModule,
isBuiltinModule,
isJSONModule,
isThirdPartyModule
isThirdPartyModule,
isTypeScriptModule
} from "./modules/walkers/ecmascript/module-resolver.js";
import {
buildPathAliases,
Expand Down Expand Up @@ -293,7 +294,7 @@ export class Skott {
this.fileReader
);

if (path.extname(entrypointModulePath) === ".ts") {
if (isTypeScriptModule(entrypointModulePath)) {
this.#moduleWalker = new TypeScriptModuleWalker();
await buildPathAliases(this.fileReader);
}
Expand Down
5 changes: 5 additions & 0 deletions packages/skott/test/ecmascript/javascript.spec.ts
Expand Up @@ -2,6 +2,7 @@

import { expect } from "chai";

import { makeTestSuiteForJsxOrTsx as makeTestSuiteForJsx } from "./jsx-and-tsx";
import {
buildSkottProjectUsingInMemoryFileExplorer,
fakeNodeBody,
Expand Down Expand Up @@ -689,4 +690,8 @@ describe("When traversing a JavaScript/Node.js project", () => {
});
});
});

describe("When the project uses JSX", () => {
makeTestSuiteForJsx("js");
});
});
167 changes: 167 additions & 0 deletions packages/skott/test/ecmascript/jsx-and-tsx.ts
@@ -0,0 +1,167 @@
import { expect } from "chai";

import {
buildSkottProjectUsingInMemoryFileExplorer,
fakeNodeBody,
mountFakeFileSystem
} from "./shared";

export function makeTestSuiteForJsxOrTsx(rawLanguage: "ts" | "js"): void {
const suiteLanguageLabel = rawLanguage === "ts" ? "TSX" : "JSX";
const jsxOrTsx = rawLanguage === "ts" ? "tsx" : "jsx";

describe(`When the project uses ${suiteLanguageLabel} files`, () => {
describe(`When the entrypoint is a ${suiteLanguageLabel} file`, () => {
it("should extract imports declarations from it", async () => {
mountFakeFileSystem({
[`index.${jsxOrTsx}`]: `
import { foo } from "./lib";
const a = (
<div>
{["foo", "bar"].map(function (i) {
return <span>{i / 2}</span>;
})}
</div>
);
`,
[`lib.${rawLanguage}`]: `
export const foo = { doSomething: () => 'Hello, world!' };
`
});

const { graph } = await buildSkottProjectUsingInMemoryFileExplorer(
`index.${jsxOrTsx}`
);

expect(graph).to.be.deep.equal({
[`index.${jsxOrTsx}`]: {
adjacentTo: [`lib.${rawLanguage}`],
id: `index.${jsxOrTsx}`,
body: fakeNodeBody
},
[`lib.${rawLanguage}`]: {
adjacentTo: [],
id: `lib.${rawLanguage}`,
body: fakeNodeBody
}
});
});
});

describe(`When there are both index.${rawLanguage} and index.${jsxOrTsx}`, () => {
describe("When implicitely refering to the index located in folder", () => {
it(`should resolve to the index.${rawLanguage}`, async () => {
mountFakeFileSystem({
[`index.${rawLanguage}`]: `
import "./lib";
`,
[`lib/index.${jsxOrTsx}`]: `
import { foo } from "./lib";
const a = (
<div>
Hello World
</div>
);
`,
[`lib/foo.${rawLanguage}`]: ``,
[`lib/index.${rawLanguage}`]: `
import "./bar";
export const foo = { doSomething: () => 'Hello, world!' };
`,
[`lib/bar.${rawLanguage}`]: ``
});

const { graph } = await buildSkottProjectUsingInMemoryFileExplorer(
`index.${rawLanguage}`
);

expect(graph).to.be.deep.equal({
[`index.${rawLanguage}`]: {
adjacentTo: [`lib/index.${rawLanguage}`],
id: `index.${rawLanguage}`,
body: fakeNodeBody
},
[`lib/index.${rawLanguage}`]: {
adjacentTo: [`lib/bar.${rawLanguage}`],
id: `lib/index.${rawLanguage}`,
body: fakeNodeBody
},
[`lib/bar.${rawLanguage}`]: {
adjacentTo: [],
id: `lib/bar.${rawLanguage}`,
body: fakeNodeBody
}
});
});
});

describe(`When explicitely refering to a file that both exist with .${rawLanguage} or .${jsxOrTsx} extension`, () => {
it(`should resolve to the index.${rawLanguage}`, async () => {
mountFakeFileSystem({
[`index.${rawLanguage}`]: `
import "./lib/component";
import "./lib/index";
`,
[`lib/component.${jsxOrTsx}`]: ``,
[`lib/component.${rawLanguage}`]: `
const a = (
<div>
Hello
</div>
);
`,
[`lib/index.${jsxOrTsx}`]: `
import { foo } from "./lib";
const a = (
<div>
{["foo", "bar"].map(function (i) {
return <span>{i / 2}</span>;
})}
</div>
);
`,
[`lib/foo.${rawLanguage}`]: ``,
[`lib/index.${rawLanguage}`]: `
import "./bar";
export const foo = { doSomething: () => 'Hello, world!' };
`,
[`lib/bar.${rawLanguage}`]: ``
});

const { graph } = await buildSkottProjectUsingInMemoryFileExplorer(
`index.${rawLanguage}`
);

expect(graph).to.be.deep.equal({
[`index.${rawLanguage}`]: {
adjacentTo: [
`lib/component.${rawLanguage}`,
`lib/index.${rawLanguage}`
],
id: `index.${rawLanguage}`,
body: fakeNodeBody
},
[`lib/index.${rawLanguage}`]: {
adjacentTo: [`lib/bar.${rawLanguage}`],
id: `lib/index.${rawLanguage}`,
body: fakeNodeBody
},
[`lib/bar.${rawLanguage}`]: {
adjacentTo: [],
id: `lib/bar.${rawLanguage}`,
body: fakeNodeBody
},
[`lib/component.${rawLanguage}`]: {
adjacentTo: [],
id: `lib/component.${rawLanguage}`,
body: fakeNodeBody
}
});
});
});
});
});
}
5 changes: 5 additions & 0 deletions packages/skott/test/ecmascript/typescript.spec.ts
@@ -1,5 +1,6 @@
import { expect } from "chai";

import { makeTestSuiteForJsxOrTsx as makeTestSuiteForTsx } from "./jsx-and-tsx";
import {
buildSkottProjectUsingInMemoryFileExplorer,
fakeNodeBody,
Expand Down Expand Up @@ -382,4 +383,8 @@ describe("When traversing a TypeScript project", () => {
});
});
});

describe("When the project uses TSX files", () => {
makeTestSuiteForTsx("ts");
});
});

0 comments on commit eb69c51

Please sign in to comment.