Skip to content

Commit

Permalink
Add runtime validation for options (#468)
Browse files Browse the repository at this point in the history
Fixes #467

This uses the libraries ts-interface-builder and ts-interface-checker to codegen
a data structure of the options TS type and then check it on every `transform`
invocation.

I tried a few approaches here, including generating JSON schema and then
validating that, but that this seemed to be pretty smooth from a build system
standpoint.

I timed this on 1000 compilations of a large file and any difference was well
within the time variability, so it seems fine to run this type validation on
every `transform` invocation. It could potentially be moved out into a separate
routine if perf is ever an issue, though.
  • Loading branch information
alangpierce committed Aug 26, 2019
1 parent 5ae23c8 commit fff991e
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 47 deletions.
2 changes: 2 additions & 0 deletions generator/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ async function generate(): Promise<void> {
await run("./node_modules/.bin/prettier --write ./src/parser/tokenizer/types.ts");
await writeFile("./src/parser/tokenizer/readWordTree.ts", generateReadWordTree());
await run("./node_modules/.bin/prettier --write ./src/parser/tokenizer/readWordTree.ts");
await run("./node_modules/.bin/ts-interface-builder src/Options.ts --suffix -gen-types");
await run("./node_modules/.bin/prettier --write ./src/Options-gen-types.ts");
console.log("Done with code generation.");
}

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"nyc": "^14.1.1",
"prettier": "^1.17.1",
"sucrase": "3.10.1",
"ts-interface-builder": "^0.1.8",
"tslint": "^5.17.0",
"typescript": "3.4",
"typescript-tslint-plugin": "^0.4.0",
Expand All @@ -78,7 +79,8 @@
"commander": "^2.20.0",
"lines-and-columns": "^1.1.6",
"mz": "^2.7.0",
"pirates": "^4.0.1"
"pirates": "^4.0.1",
"ts-interface-checker": "^0.1.7"
},
"engines": {
"node": ">=8"
Expand Down
35 changes: 35 additions & 0 deletions src/Options-gen-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* This module was automatically generated by `ts-interface-builder`
*/
import * as t from "ts-interface-checker";
// tslint:disable:object-literal-key-quotes

export const Transform = t.union(
t.lit("jsx"),
t.lit("typescript"),
t.lit("flow"),
t.lit("imports"),
t.lit("react-hot-loader"),
);

export const SourceMapOptions = t.iface([], {
compiledFilename: "string",
});

export const Options = t.iface([], {
transforms: t.array("Transform"),
jsxPragma: t.opt("string"),
jsxFragmentPragma: t.opt("string"),
enableLegacyTypeScriptModuleInterop: t.opt("boolean"),
enableLegacyBabel5ModuleInterop: t.opt("boolean"),
sourceMapOptions: t.opt("SourceMapOptions"),
filePath: t.opt("string"),
production: t.opt("boolean"),
});

const exportedTypeSuite: t.ITypeSuite = {
Transform,
SourceMapOptions,
Options,
};
export default exportedTypeSuite;
52 changes: 52 additions & 0 deletions src/Options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {createCheckers} from "ts-interface-checker";
import OptionsGenTypes from "./Options-gen-types";

const {Options: OptionsChecker} = createCheckers(OptionsGenTypes);

export type Transform = "jsx" | "typescript" | "flow" | "imports" | "react-hot-loader";

export interface SourceMapOptions {
/**
* The name to use in the "file" field of the source map. This should be the name of the compiled
* file.
*/
compiledFilename: string;
}

export interface Options {
transforms: Array<Transform>;
/**
* If specified, function name to use in place of React.createClass when compiling JSX.
*/
jsxPragma?: string;
/**
* If specified, function name to use in place of React.Fragment when compiling JSX.
*/
jsxFragmentPragma?: string;
/**
* If true, replicate the import behavior of TypeScript's esModuleInterop: false.
*/
enableLegacyTypeScriptModuleInterop?: boolean;
/**
* If true, replicate the import behavior Babel 5 and babel-plugin-add-module-exports.
*/
enableLegacyBabel5ModuleInterop?: boolean;
/**
* If specified, we also return a RawSourceMap object alongside the code. Currently, source maps
* simply map each line to the original line without any mappings within lines, since Sucrase
* preserves line numbers. filePath must be specified if this option is enabled.
*/
sourceMapOptions?: SourceMapOptions;
/**
* File path to use in error messages, React display names, and source maps.
*/
filePath?: string;
/**
* If specified, omit any development-specific code in the output.
*/
production?: boolean;
}

export function validateOptions(options: Options): void {
OptionsChecker.strictCheck(options);
}
51 changes: 7 additions & 44 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,14 @@ import CJSImportProcessor from "./CJSImportProcessor";
import computeSourceMap, {RawSourceMap} from "./computeSourceMap";
import identifyShadowedGlobals from "./identifyShadowedGlobals";
import NameManager from "./NameManager";
import {validateOptions} from "./Options";
import {parse} from "./parser";
import {Scope} from "./parser/tokenizer/state";
import TokenProcessor from "./TokenProcessor";
import RootTransformer from "./transformers/RootTransformer";
import formatTokens from "./util/formatTokens";
import getTSImportedNames from "./util/getTSImportedNames";

export type Transform = "jsx" | "typescript" | "flow" | "imports" | "react-hot-loader";

export interface SourceMapOptions {
/**
* The name to use in the "file" field of the source map. This should be the name of the compiled
* file.
*/
compiledFilename: string;
}

export interface Options {
transforms: Array<Transform>;
/**
* If specified, function name to use in place of React.createClass when compiling JSX.
*/
jsxPragma?: string;
/**
* If specified, function name to use in place of React.Fragment when compiling JSX.
*/
jsxFragmentPragma?: string;
/**
* If true, replicate the import behavior of TypeScript's esModuleInterop: false.
*/
enableLegacyTypeScriptModuleInterop?: boolean;
/**
* If true, replicate the import behavior Babel 5 and babel-plugin-add-module-exports.
*/
enableLegacyBabel5ModuleInterop?: boolean;
/**
* If specified, we also return a RawSourceMap object alongside the code. Currently, source maps
* simply map each line to the original line without any mappings within lines, since Sucrase
* preserves line numbers. filePath must be specified if this option is enabled.
*/
sourceMapOptions?: SourceMapOptions;
/**
* File path to use in error messages, React display names, and source maps.
*/
filePath?: string;
/**
* If specified, omit any development-specific code in the output.
*/
production?: boolean;
}

export interface TransformResult {
code: string;
sourceMap?: RawSourceMap;
Expand All @@ -65,12 +22,18 @@ export interface SucraseContext {
importProcessor: CJSImportProcessor | null;
}

// Re-export options types in an isolatedModules-friendly way so they can be used externally.
export type Options = import("./Options").Options;
export type SourceMapOptions = import("./Options").SourceMapOptions;
export type Transform = import("./Options").Transform;

export function getVersion(): string {
// eslint-disable-next-line
return require("../package.json").version;
}

export function transform(code: string, options: Options): TransformResult {
validateOptions(options);
try {
const sucraseContext = getSucraseContext(code, options);
const transformer = new RootTransformer(
Expand Down
44 changes: 42 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=

commander@^2.12.1, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1, commander@~2.20.0:
commander@^2.12.1, commander@^2.12.2, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1, commander@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
Expand Down Expand Up @@ -1369,6 +1369,15 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"

fs-extra@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"

fs-minipass@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
Expand Down Expand Up @@ -1482,7 +1491,7 @@ globals@^11.1.0, globals@^11.7.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==

graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
version "4.2.2"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02"
integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==
Expand Down Expand Up @@ -1943,6 +1952,13 @@ json5@^2.1.0:
dependencies:
minimist "^1.2.0"

jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
optionalDependencies:
graceful-fs "^4.1.6"

kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
Expand Down Expand Up @@ -3378,6 +3394,20 @@ trim-right@^1.0.1:
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=

ts-interface-builder@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/ts-interface-builder/-/ts-interface-builder-0.1.8.tgz#0bd5a7d8d24f46c12b1dcdf81c44dd7704e7a11b"
integrity sha512-uUKE9+XTRfSp8X/LLfnqnS2uI2ReS77GxFE1aD4Y7BIBXpuuYKIJbWjGWTuFU8PdLhFSam8wYP1HmY4nbcqv+g==
dependencies:
commander "^2.12.2"
fs-extra "^4.0.3"
typescript "^2.6.2"

ts-interface-checker@^0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.7.tgz#77977d82510444974bd5684041a16d4a0de7763a"
integrity sha512-nneiPmUk3zegVC5NA7bvNMgzmcljidic35Z6gMDNggVd8xKBnLzVVZzm/Cdz0LIU1u6uw3qcwuWfKQnyRL8+ww==

tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
Expand Down Expand Up @@ -3435,6 +3465,11 @@ typescript@3.4:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99"
integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==

typescript@^2.6.2:
version "2.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==

uglify-js@^3.1.4:
version "3.6.0"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
Expand All @@ -3453,6 +3488,11 @@ union-value@^1.0.0:
is-extendable "^0.1.1"
set-value "^2.0.1"

universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==

unset-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
Expand Down

0 comments on commit fff991e

Please sign in to comment.