Skip to content

add --strict option #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 21, 2025
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ TypeScript2Python supports many of TypeScripts type constructs, including:

## Transpiler options

### Strict

Use the `--strict` flag to enable all strict type-checking options to ensure `undefined` and `null` properties are not ignored during transpilation.

### Nullable optionals

In TypeScript objects, optional values can also be set to `undefined`. By default we assume the according Python
Expand Down
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import path from "path";
import { program } from "@commander-js/extra-typings";
import { typeScriptToPython } from "./typeScriptToPython";
import { Ts2PyConfig } from "./config";
import { readFileSync } from "fs";

const compile = (fileNames: string[], config: Ts2PyConfig) => {
const compile = (fileNames: string[], config: Ts2PyConfig & {strict?: boolean}) => {
const program = ts.createProgram(fileNames, {
noEmit: true,
allowJs: true,
resolveJsonModule: true,
skipLibCheck: true,
strict: config.strict,
});

const relevantSourceFiles = program
Expand All @@ -21,7 +23,6 @@ const compile = (fileNames: string[], config: Ts2PyConfig) => {
.map((fn) => path.relative(fn, f.fileName) === "")
.reduce((a, b) => a || b),
);

const transpiled = typeScriptToPython(program.getTypeChecker(), relevantSourceFiles, config)
console.log(transpiled);
}
Expand All @@ -30,6 +31,7 @@ program
.name("typescript2python")
.description("A program that converts TypeScript type definitions to Python")
.option("--nullable-optionals", "if set, optional entries in dictionaries will be nullable, e.g. `NotRequired[Optional[T]]`")
.option("--strict", "Enable all strict type-checking options.")
.arguments("<input...>")
.action((args, options) => {
compile(args, options)
Expand Down
19 changes: 19 additions & 0 deletions src/testing/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,30 @@ describe("transpiling basic types", () => {
"export type T = number | string | Record<string, boolean>",
"from typing_extensions import Dict, Union\n\nT = Union[str,float,Dict[str,bool]]",
],
[
"export type T = number | undefined",
// without strict mode the `undefined` gets lost here
"T = float",
],
])("transpiles %p to %p", async (input, expected) => {
const result = await transpileString(input);
expect(result).toEqual(expected);
});

it.each([
[
"export type T = number | undefined",
"from typing_extensions import Union\n\nT = Union[None,float]",
],
[
"export type T = number | null",
"from typing_extensions import Union\n\nT = Union[None,float]",
],
])("transpiles %p to %p when strict", async (input, expected) => {
const result = await transpileString(input, {}, { strict: true });
expect(result).toEqual(expected);
});

it("only transpiles exported types", async () => {
const result = await transpileString(`
type NotExported = number;
Expand Down
2 changes: 1 addition & 1 deletion src/testing/dicts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class A(TypedDict):
expect(result).toContain(`class A(TypedDict):\n foo: NotRequired[str]`);
});

it("transpiles optional values with non-null optionals as NotRequired[T]", async () => {
it.only("transpiles optional values with non-null optionals as NotRequired[T]", async () => {
const result = await transpileString(`export type A = { foo?: string }`, {
nullableOptionals: true,
});
Expand Down
11 changes: 9 additions & 2 deletions src/testing/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ import { createProject, ts } from "@ts-morph/bootstrap";
**/
let globalProject: ReturnType<typeof createProject> | undefined;

/** Each file should get a unique name to avoid issues. */
let i = 0;

export const transpileString = async (
code: string,
config: Ts2PyConfig = {},
compilerOptions: ts.CompilerOptions = {},
) => {
if (globalProject === undefined) {
globalProject = createProject({
Expand All @@ -19,11 +23,14 @@ export const transpileString = async (
}

const project = await globalProject;
const fileName = `source.ts`;
const fileName = `source_${i++}.ts`;

// instead of adding a new source file for each program, we update the existing one.
const sourceFile = project.updateSourceFile(fileName, code);
const program = project.createProgram();
const program = project.createProgram({
rootNames: [fileName],
options: { ...project.compilerOptions, ...compilerOptions },
});
const diagnostics = ts.getPreEmitDiagnostics(program);

if (diagnostics.length > 0) {
Expand Down
Loading