From 47702cb3a33bb248e7aa2fdde5b05fcb4b523614 Mon Sep 17 00:00:00 2001 From: dhruvdutt Date: Fri, 13 Jul 2018 17:39:51 +0530 Subject: [PATCH] feat(utils): add typescript support --- package-lock.json | 6 + package.json | 1 + packages/generators/add-generator.ts | 4 +- packages/generators/remove-generator.js | 122 ---- packages/generators/remove-generator.ts | 2 +- packages/init/index.ts | 8 +- packages/init/init.ts | 9 +- .../migrate/bannerPlugin/bannerPlugin.test.js | 2 +- .../extractTextPlugin.test.js | 2 +- packages/migrate/index.ts | 2 +- .../loaderOptionsPlugin.test.js | 2 +- .../loaderOptionsPlugin.ts | 2 +- packages/migrate/loaders/loaders.test.js | 2 +- packages/migrate/loaders/loaders.ts | 8 +- .../moduleConcatenationPlugin.test.js | 2 +- .../namedModulesPlugin.test.js | 2 +- .../noEmitOnErrorsPlugin.test.js | 2 +- .../migrate/outputPath/outputPath.test.js | 2 +- packages/migrate/outputPath/outputPath.ts | 2 +- .../removeDeprecatedPlugins.test.js | 2 +- .../removeJsonLoader/removeJsonLoader.test.js | 2 +- packages/migrate/resolve/resolve.test.js | 2 +- packages/migrate/resolve/resolve.ts | 2 +- packages/migrate/types/NodePath.ts | 70 +- .../uglifyJsPlugin/uglifyJsPlugin.test.js | 2 +- packages/utils/.gitignore | 6 + packages/utils/.npmignore | 2 + packages/utils/{ast-utils.js => ast-utils.ts} | 407 ++++++------ .../utils/{copy-utils.js => copy-utils.ts} | 45 +- .../utils/{defineTest.js => defineTest.ts} | 74 ++- packages/utils/{index.js => index.ts} | 0 packages/utils/is-local-path.test.js | 2 +- .../{is-local-path.js => is-local-path.ts} | 10 +- packages/utils/modify-config-helper.js | 104 --- packages/utils/modify-config-helper.ts | 126 ++++ packages/utils/npm-exists.js | 23 - packages/utils/npm-exists.test.js | 2 +- packages/utils/npm-exists.ts | 22 + packages/utils/npm-packages-exists.test.js | 5 +- ...kages-exists.js => npm-packages-exists.ts} | 31 +- packages/utils/package-lock.json | 54 ++ ...{package-manager.js => package-manager.ts} | 53 +- packages/utils/package.json | 18 +- .../utils/{prop-types.js => prop-types.ts} | 10 +- packages/utils/recursive-parser.js | 54 -- packages/utils/recursive-parser.test.js | 2 +- packages/utils/recursive-parser.ts | 60 ++ ...esolve-packages.js => resolve-packages.ts} | 47 +- .../{run-prettier.js => run-prettier.ts} | 26 +- packages/utils/scaffold.js | 102 --- packages/utils/scaffold.ts | 108 +++ packages/utils/tsconfig.json | 3 + packages/utils/types/NodePath.js | 2 - packages/utils/types/NodePath.ts | 93 +++ packages/utils/types/Yeoman.js | 2 - packages/utils/types/Yeoman.ts | 12 + packages/utils/types/index.js | 0 packages/utils/types/index.ts | 4 + packages/utils/validate-identifier.js | 613 ----------------- packages/utils/validate-identifier.ts | 618 ++++++++++++++++++ 60 files changed, 1574 insertions(+), 1428 deletions(-) delete mode 100644 packages/generators/remove-generator.js create mode 100644 packages/utils/.gitignore rename packages/utils/{ast-utils.js => ast-utils.ts} (61%) rename packages/utils/{copy-utils.js => copy-utils.ts} (58%) rename packages/utils/{defineTest.js => defineTest.ts} (76%) rename packages/utils/{index.js => index.ts} (100%) rename packages/utils/{is-local-path.js => is-local-path.ts} (84%) delete mode 100644 packages/utils/modify-config-helper.js create mode 100644 packages/utils/modify-config-helper.ts delete mode 100644 packages/utils/npm-exists.js create mode 100644 packages/utils/npm-exists.ts rename packages/utils/{npm-packages-exists.js => npm-packages-exists.ts} (70%) rename packages/utils/{package-manager.js => package-manager.ts} (60%) rename packages/utils/{prop-types.js => prop-types.ts} (85%) delete mode 100644 packages/utils/recursive-parser.js create mode 100644 packages/utils/recursive-parser.ts rename packages/utils/{resolve-packages.js => resolve-packages.ts} (66%) rename packages/utils/{run-prettier.js => run-prettier.ts} (61%) delete mode 100644 packages/utils/scaffold.js create mode 100644 packages/utils/scaffold.ts create mode 100644 packages/utils/tsconfig.json delete mode 100644 packages/utils/types/NodePath.js create mode 100644 packages/utils/types/NodePath.ts delete mode 100644 packages/utils/types/Yeoman.js create mode 100644 packages/utils/types/Yeoman.ts delete mode 100644 packages/utils/types/index.js create mode 100644 packages/utils/types/index.ts delete mode 100644 packages/utils/validate-identifier.js create mode 100644 packages/utils/validate-identifier.ts diff --git a/package-lock.json b/package-lock.json index e26ff82a344..e83c1b079b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -975,6 +975,12 @@ "any-observable": "^0.3.0" } }, + "@types/jest": { + "version": "23.1.6", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.1.6.tgz", + "integrity": "sha512-lBu2tjrfGuj0gARErNmHZJrnWBdRrYk2XqlBY3LRv8Dqxk3w3461uuFMKmwfDDiOa5kzXocUnunCBBacGwF3+A==", + "dev": true + }, "@types/node": { "version": "10.5.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.2.tgz", diff --git a/package.json b/package.json index 4ccca59bb2a..5a2f110ee77 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "@commitlint/prompt-cli": "^7.0.0", "@commitlint/travis-cli": "^7.0.0", "@types/node": "^10.5.1", + "@types/jest": "^23.1.1", "bundlesize": "^0.17.0", "codecov": "^3.0.2", "commitizen": "^2.10.1", diff --git a/packages/generators/add-generator.ts b/packages/generators/add-generator.ts index 3d4ce1b1ffa..127a74f5de3 100644 --- a/packages/generators/add-generator.ts +++ b/packages/generators/add-generator.ts @@ -5,9 +5,9 @@ import * as autoComplete from "inquirer-autocomplete-prompt"; import * as path from "path"; import webpackDevServerSchema from "webpack-dev-server/lib/optionsSchema.json"; -import * as npmExists from "@webpack-cli/utils/npm-exists"; +import npmExists from "@webpack-cli/utils/npm-exists"; import { getPackageManager } from "@webpack-cli/utils/package-manager"; -import * as PROP_TYPES from "@webpack-cli/utils/prop-types"; +import PROP_TYPES from "@webpack-cli/utils/prop-types"; import { AutoComplete, Confirm, diff --git a/packages/generators/remove-generator.js b/packages/generators/remove-generator.js deleted file mode 100644 index 0a2727e5b8c..00000000000 --- a/packages/generators/remove-generator.js +++ /dev/null @@ -1,122 +0,0 @@ -const Generator = require("yeoman-generator"); -const path = require("path"); -const fs = require("fs"); -const { List } = require("@webpack-cli/webpack-scaffold"); - -const PROP_TYPES = require("@webpack-cli/utils/prop-types"); - -/** - * - * Generator for removing properties - * @class RemoveGenerator - * @extends Generator - * @returns {Void} After execution, transforms are triggered - * - */ - -module.exports = class RemoveGenerator extends Generator { - constructor(args, opts) { - super(args, opts); - this.configuration = { - config: { - webpackOptions: {}, - } - }; - - let configPath = path.resolve(process.cwd(), "webpack.config.js"); - const webpackConfigExists = fs.existsSync(configPath); - if (!webpackConfigExists) { - configPath = null; - // end the generator stating webpack config not found or to specify the config - } - this.webpackOptions = require(configPath); - } - - getPropTypes() { - return Object.keys(this.webpackOptions); - } - - getModuleLoaders() { - if (this.webpackOptions.module && this.webpackOptions.module.rules) { - return this.webpackOptions.module.rules.map(rule => rule ? rule.loader : null); - } - } - - prompting() { - const done = this.async(); - let propValue; - - return this.prompt([ - List( - "propType", - "Which property do you want to remove?", - Array.from(this.getPropTypes()) - ) - ]) - .then(({ propType }) => { - if (!PROP_TYPES.has(propType)) { - console.log("Invalid webpack config prop"); - return; - } - - propValue = this.webpackOptions[propType]; - if (typeof propValue === "object") { - if (Array.isArray(propValue)) { - return this.prompt([ - List( - "keyType", - `Which key do you want to remove from ${propType}?`, - Array.from(propValue) - ) - ]).then(({ keyType }) => { - this.configuration.config.webpackOptions[propType] = [ keyType ]; - }); - } else { - return this.prompt([ - List( - "keyType", - `Which key do you want to remove from ${propType}?`, - Array.from(Object.keys(propValue)) - ) - ]) - .then(({ keyType }) => { - if (propType === "module" && keyType === "rules") { - return this.prompt([ - List( - "rule", - "Which loader do you want to remove?", - Array.from(this.getModuleLoaders()) - ) - ]) - .then(({ rule }) => { - const loaderIndex = this.getModuleLoaders().indexOf(rule); - const loader = this.webpackOptions.module.rules[loaderIndex]; - this.configuration.config.webpackOptions.module = { - rules: [ loader ] - }; - }); - } else { - // remove the complete prop object if there is only one key - if (Object.keys(this.webpackOptions[propType]).length <= 1) { - this.configuration.config.webpackOptions[propType] = null; - } else { - this.configuration.config.webpackOptions[propType] = { - [keyType]: null - }; - } - } - }); - } - } else { - this.configuration.config.webpackOptions[propType] = null; - } - }) - .then(() => { - done(); - }); - } - - writing() { - this.config.set("configuration", this.configuration); - } -}; diff --git a/packages/generators/remove-generator.ts b/packages/generators/remove-generator.ts index 7c6fc063021..768b764ed6d 100644 --- a/packages/generators/remove-generator.ts +++ b/packages/generators/remove-generator.ts @@ -2,7 +2,7 @@ import * as fs from "fs"; import * as path from "path"; import Generator = require("yeoman-generator"); -import * as PROP_TYPES from "@webpack-cli/utils/prop-types"; +import PROP_TYPES from "@webpack-cli/utils/prop-types"; import { List } from "@webpack-cli/webpack-scaffold"; import { IWebpackOptions } from "./types"; diff --git a/packages/init/index.ts b/packages/init/index.ts index 46a1dace60c..bb80bd0fae3 100644 --- a/packages/init/index.ts +++ b/packages/init/index.ts @@ -1,6 +1,6 @@ -import * as defaultGenerator from "@webpack-cli/generators/init-generator"; -import * as modifyConfigHelper from "@webpack-cli/utils/modify-config-helper"; -import * as npmPackagesExists from "@webpack-cli/utils/npm-packages-exists"; +import defaultGenerator from "@webpack-cli/generators/init-generator"; +import modifyConfigHelper from "@webpack-cli/utils/modify-config-helper"; +import npmPackagesExists from "@webpack-cli/utils/npm-packages-exists"; /** * @@ -13,7 +13,7 @@ import * as npmPackagesExists from "@webpack-cli/utils/npm-packages-exists"; * followed up with a yeoman instance of that if there's packages. If not, it creates a defaultGenerator */ -export default function initializeInquirer(...args: string[]): Function { +export default function initializeInquirer(...args: string[]): Function | void { const packages: string[] = args.slice(3); if (packages.length === 0) { diff --git a/packages/init/init.ts b/packages/init/init.ts index 9e721f94371..e841441cc03 100644 --- a/packages/init/init.ts +++ b/packages/init/init.ts @@ -3,10 +3,11 @@ import * as j from "jscodeshift"; import pEachSeries = require("p-each-series"); import * as path from "path"; -import * as propTypes from "@webpack-cli/utils/prop-types"; -import * as astTransform from "@webpack-cli/utils/recursive-parser"; -import * as runPrettier from "@webpack-cli/utils/run-prettier"; +import propTypes from "@webpack-cli/utils/prop-types"; +import astTransform from "@webpack-cli/utils/recursive-parser"; +import runPrettier from "@webpack-cli/utils/run-prettier"; +import { INode } from "@webpack-cli/utils/types/NodePath"; import { IError } from "./types"; import { IConfiguration, IWebpackProperties } from "./types/Transform"; @@ -53,7 +54,7 @@ export default function runTransform(webpackProperties: IWebpackProperties, acti ); const transformAction: string | null = action || null; - return pEachSeries(transformations, (f: string): Promise => { + return pEachSeries(transformations, (f: string): boolean | INode => { return astTransform(j, ast, config.webpackOptions[f], transformAction, f); }) .then((_?: any) => { diff --git a/packages/migrate/bannerPlugin/bannerPlugin.test.js b/packages/migrate/bannerPlugin/bannerPlugin.test.js index c8b01809057..8f6ad100036 100644 --- a/packages/migrate/bannerPlugin/bannerPlugin.test.js +++ b/packages/migrate/bannerPlugin/bannerPlugin.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "bannerPlugin", "bannerPlugin-0"); defineTest(__dirname, "bannerPlugin", "bannerPlugin-1"); diff --git a/packages/migrate/extractTextPlugin/extractTextPlugin.test.js b/packages/migrate/extractTextPlugin/extractTextPlugin.test.js index 14239d5749b..b003fcc520a 100644 --- a/packages/migrate/extractTextPlugin/extractTextPlugin.test.js +++ b/packages/migrate/extractTextPlugin/extractTextPlugin.test.js @@ -1,5 +1,5 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "extractTextPlugin"); diff --git a/packages/migrate/index.ts b/packages/migrate/index.ts index 36ef807c6cb..30adb7fc4b3 100644 --- a/packages/migrate/index.ts +++ b/packages/migrate/index.ts @@ -7,7 +7,7 @@ import pLazy = require("p-lazy"); import * as path from "path"; import { validate, WebpackOptionsValidationError } from "webpack"; -import * as runPrettier from "@webpack-cli/utils/run-prettier"; +import runPrettier from "@webpack-cli/utils/run-prettier"; import { transformations } from "./migrate"; import { IJSCodeshift, INode } from "./types/NodePath"; diff --git a/packages/migrate/loaderOptionsPlugin/loaderOptionsPlugin.test.js b/packages/migrate/loaderOptionsPlugin/loaderOptionsPlugin.test.js index e5f47eefee8..c8928b75473 100644 --- a/packages/migrate/loaderOptionsPlugin/loaderOptionsPlugin.test.js +++ b/packages/migrate/loaderOptionsPlugin/loaderOptionsPlugin.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "loaderOptionsPlugin", "loaderOptionsPlugin-0"); defineTest(__dirname, "loaderOptionsPlugin", "loaderOptionsPlugin-1"); diff --git a/packages/migrate/loaderOptionsPlugin/loaderOptionsPlugin.ts b/packages/migrate/loaderOptionsPlugin/loaderOptionsPlugin.ts index 2e4d6d4d1c8..681ff9579cd 100644 --- a/packages/migrate/loaderOptionsPlugin/loaderOptionsPlugin.ts +++ b/packages/migrate/loaderOptionsPlugin/loaderOptionsPlugin.ts @@ -23,7 +23,7 @@ interface ILoaderOptions { * */ -export default function(j: IJSCodeshift, ast: INode): void { +export default function(j: IJSCodeshift, ast: INode): INode { const loaderOptions: ILoaderOptions = {}; // If there is debug: true, set debug: true in the plugin diff --git a/packages/migrate/loaders/loaders.test.js b/packages/migrate/loaders/loaders.test.js index 31d50eeabed..4dc548b6bc3 100644 --- a/packages/migrate/loaders/loaders.test.js +++ b/packages/migrate/loaders/loaders.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "loaders", "loaders-0"); defineTest(__dirname, "loaders", "loaders-1"); diff --git a/packages/migrate/loaders/loaders.ts b/packages/migrate/loaders/loaders.ts index 187b7e8a08e..e3b5b920fbb 100644 --- a/packages/migrate/loaders/loaders.ts +++ b/packages/migrate/loaders/loaders.ts @@ -209,7 +209,7 @@ export default function(j: IJSCodeshift, ast: INode): INode { * @returns {Node} ast - jscodeshift ast */ - const prepostLoaders = (_?: void): void => + const prepostLoaders = (_?: void): INode => ast .find(j.ObjectExpression) .filter((p: INode): boolean => utils.findObjWithOneOfKeys(p, ["preLoaders", "postLoaders"])) @@ -221,7 +221,7 @@ export default function(j: IJSCodeshift, ast: INode): INode { * @returns {Node} ast - jscodeshift ast */ - const loadersToRules = (_?: void): void => + const loadersToRules = (_?: void): INode => ast .find(j.Identifier) .filter(checkForLoader) @@ -308,7 +308,7 @@ export default function(j: IJSCodeshift, ast: INode): INode { * @returns {Node} ast - jscodeshift ast */ - const addLoaderSuffix = (_?: void): void => + const addLoaderSuffix = (_?: void): INode => ast.find(j.ObjectExpression).forEach((path: INode): void => { path.value.properties.forEach((prop: INode): void => { if ( @@ -360,7 +360,7 @@ export default function(j: IJSCodeshift, ast: INode): INode { * @returns {Node} ast - jscodeshift ast */ - const moveOptionsToUse = (_?: void): void => + const moveOptionsToUse = (_?: void): INode => ast .find(j.ObjectExpression) .filter((p: INode): boolean => utils.findObjWithOneOfKeys(p, ["use"])) diff --git a/packages/migrate/moduleConcatenationPlugin/moduleConcatenationPlugin.test.js b/packages/migrate/moduleConcatenationPlugin/moduleConcatenationPlugin.test.js index 31cecf5b632..9a895b1c50e 100644 --- a/packages/migrate/moduleConcatenationPlugin/moduleConcatenationPlugin.test.js +++ b/packages/migrate/moduleConcatenationPlugin/moduleConcatenationPlugin.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest( __dirname, diff --git a/packages/migrate/namedModulesPlugin/namedModulesPlugin.test.js b/packages/migrate/namedModulesPlugin/namedModulesPlugin.test.js index 0c8bf18db73..b0a0b14969a 100644 --- a/packages/migrate/namedModulesPlugin/namedModulesPlugin.test.js +++ b/packages/migrate/namedModulesPlugin/namedModulesPlugin.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "namedModulesPlugin", "namedModulesPlugin-0"); defineTest(__dirname, "namedModulesPlugin", "namedModulesPlugin-1"); diff --git a/packages/migrate/noEmitOnErrorsPlugin/noEmitOnErrorsPlugin.test.js b/packages/migrate/noEmitOnErrorsPlugin/noEmitOnErrorsPlugin.test.js index fa9a9e2fd48..fc7c0663748 100644 --- a/packages/migrate/noEmitOnErrorsPlugin/noEmitOnErrorsPlugin.test.js +++ b/packages/migrate/noEmitOnErrorsPlugin/noEmitOnErrorsPlugin.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "noEmitOnErrorsPlugin", "noEmitOnErrorsPlugin-0"); defineTest(__dirname, "noEmitOnErrorsPlugin", "noEmitOnErrorsPlugin-1"); diff --git a/packages/migrate/outputPath/outputPath.test.js b/packages/migrate/outputPath/outputPath.test.js index 2763dbb922c..f103e99a09e 100644 --- a/packages/migrate/outputPath/outputPath.test.js +++ b/packages/migrate/outputPath/outputPath.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "outputPath", "outputPath-0"); defineTest(__dirname, "outputPath", "outputPath-1"); diff --git a/packages/migrate/outputPath/outputPath.ts b/packages/migrate/outputPath/outputPath.ts index 1d3e54853a6..e5038b3c9a0 100644 --- a/packages/migrate/outputPath/outputPath.ts +++ b/packages/migrate/outputPath/outputPath.ts @@ -57,7 +57,7 @@ export default function(j: IJSCodeshift, ast: INode): INode | void { .replaceWith((p: INode): INode => replaceWithPath(j, p, finalPathName)); if (!isPathPresent) { - const pathRequire: INode[] = utils.getRequire(j, "path", "path"); + const pathRequire: INode = utils.getRequire(j, "path", "path"); return ast .find(j.Program) .replaceWith((p: INode): INode => diff --git a/packages/migrate/removeDeprecatedPlugins/removeDeprecatedPlugins.test.js b/packages/migrate/removeDeprecatedPlugins/removeDeprecatedPlugins.test.js index 6574f9b58c0..e881f02428f 100644 --- a/packages/migrate/removeDeprecatedPlugins/removeDeprecatedPlugins.test.js +++ b/packages/migrate/removeDeprecatedPlugins/removeDeprecatedPlugins.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "removeDeprecatedPlugins", "removeDeprecatedPlugins-0"); defineTest(__dirname, "removeDeprecatedPlugins", "removeDeprecatedPlugins-1"); diff --git a/packages/migrate/removeJsonLoader/removeJsonLoader.test.js b/packages/migrate/removeJsonLoader/removeJsonLoader.test.js index 1cebc84d554..82e38804c86 100644 --- a/packages/migrate/removeJsonLoader/removeJsonLoader.test.js +++ b/packages/migrate/removeJsonLoader/removeJsonLoader.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "removeJsonLoader", "removeJsonLoader-0"); defineTest(__dirname, "removeJsonLoader", "removeJsonLoader-1"); diff --git a/packages/migrate/resolve/resolve.test.js b/packages/migrate/resolve/resolve.test.js index 0b4f14bac26..c3971466966 100644 --- a/packages/migrate/resolve/resolve.test.js +++ b/packages/migrate/resolve/resolve.test.js @@ -1,5 +1,5 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "resolve"); diff --git a/packages/migrate/resolve/resolve.ts b/packages/migrate/resolve/resolve.ts index 9d0f8328364..c79f5cca4c3 100644 --- a/packages/migrate/resolve/resolve.ts +++ b/packages/migrate/resolve/resolve.ts @@ -9,7 +9,7 @@ import { IJSCodeshift, INode } from "../types/NodePath"; * @returns {Node} ast - jscodeshift ast */ -export default function transformer(j: IJSCodeshift, ast: INode): INode[] | void { +export default function transformer(j: IJSCodeshift, ast: INode): INode { const getRootVal = (p: INode): INode => { return p.node.value.properties.filter((prop: INode): boolean => prop.key.name === "root")[0]; diff --git a/packages/migrate/types/NodePath.ts b/packages/migrate/types/NodePath.ts index d63f5cad350..d082ddc6587 100644 --- a/packages/migrate/types/NodePath.ts +++ b/packages/migrate/types/NodePath.ts @@ -18,7 +18,7 @@ export interface INode extends Object { }; filter?: (p: (p: INode) => boolean) => INode; find?: (objectExpression: object, filterExpression?: object) => INode; - forEach?: (p: (p: INode) => void) => void; + forEach?: (p: (p: INode) => void) => INode; get?: (property: string) => INode; remove?: (_?: void) => void; nodes?: (_?: void) => INode[]; @@ -41,49 +41,53 @@ export interface INode extends Object { toSource?: (object: { quote?: string, }) => string; - source: string; - ast: INode; - rules?: INode[]; + source?: string; + ast?: INode; + rules?: IModuleRule[]; __paths?: INode[]; } +interface IModuleRule { + loader?: string; +} + interface IExpressionObject { - name: string; + name?: string; } export interface IJSCodeshift extends Object { (source?: INode | string): INode; - withParser: (parser: string) => IJSCodeshift; - identifier: (key: string) => INode; - literal: (key: valueType) => INode; - memberExpression: (node1: INode, node2: INode, bool?: boolean) => INode; - objectProperty: (key: INode, property: valueType) => INode; - objectExpression: (properties: INode[]) => INode; - newExpression: (expression: INode, args: INode[]) => INode; - callExpression: (expression: INode, args: INode[]) => INode; - variableDeclarator: (key: INode, args: INode) => INode; - variableDeclaration: (key: string, args: INode[]) => INode; - arrayExpression: (args?: INode[]) => INode; - property: (type: string, key: INode, value: INode) => INode; - program: (nodes: INode[]) => INode; - booleanLiteral: (bool: boolean) => INode; - Property: IExpressionObject; - NewExpression: IExpressionObject; - CallExpression: IExpressionObject; - VariableDeclarator: IExpressionObject; - Identifier: IExpressionObject; - Literal: IExpressionObject; - ArrayExpression: IExpressionObject; - MemberExpression: IExpressionObject; - FunctionExpression: IExpressionObject; - ObjectExpression: IExpressionObject; - BlockStatement: IExpressionObject; - Program: IExpressionObject; - filters: { + withParser?: (parser: string) => IJSCodeshift; + identifier?: (key: string) => INode; + literal?: (key: valueType) => INode; + memberExpression?: (node1: INode, node2: INode, bool?: boolean) => INode; + objectProperty?: (key: INode, property: valueType) => INode; + objectExpression?: (properties: INode[]) => INode; + newExpression?: (expression: INode, args: INode[]) => INode; + callExpression?: (expression: INode, args: INode[]) => INode; + variableDeclarator?: (key: INode, args: INode) => INode; + variableDeclaration?: (key: string, args: INode[]) => INode; + arrayExpression?: (args?: INode[]) => INode; + property?: (type: string, key: INode, value: INode) => INode; + program?: (nodes: INode[]) => INode; + booleanLiteral?: (bool: boolean) => INode; + Property?: IExpressionObject; + NewExpression?: IExpressionObject; + CallExpression?: IExpressionObject; + VariableDeclarator?: IExpressionObject; + Identifier?: IExpressionObject; + Literal?: IExpressionObject; + ArrayExpression?: IExpressionObject; + MemberExpression?: IExpressionObject; + FunctionExpression?: IExpressionObject; + ObjectExpression?: IExpressionObject; + BlockStatement?: IExpressionObject; + Program?: IExpressionObject; + filters?: { VariableDeclarator: { requiresModule: Function, }, }; } -export type valueType = string | number | boolean | INode[] | INode | null; +export type valueType = string | number | boolean | any[] | INode | null; diff --git a/packages/migrate/uglifyJsPlugin/uglifyJsPlugin.test.js b/packages/migrate/uglifyJsPlugin/uglifyJsPlugin.test.js index 2aa4fbe622b..d60cd4ba928 100644 --- a/packages/migrate/uglifyJsPlugin/uglifyJsPlugin.test.js +++ b/packages/migrate/uglifyJsPlugin/uglifyJsPlugin.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("@webpack-cli/utils/defineTest"); +const defineTest = require("@webpack-cli/utils/defineTest").default; defineTest(__dirname, "uglifyJsPlugin", "uglifyJsPlugin-0"); defineTest(__dirname, "uglifyJsPlugin", "uglifyJsPlugin-1"); diff --git a/packages/utils/.gitignore b/packages/utils/.gitignore new file mode 100644 index 00000000000..04d5044e769 --- /dev/null +++ b/packages/utils/.gitignore @@ -0,0 +1,6 @@ +/*.js +/**/*.js +!*.test.js +!/**/*.test.js +!/**/__testfixtures__/*.js +!/**/__snapshots__/*.js diff --git a/packages/utils/.npmignore b/packages/utils/.npmignore index 1466463b66c..21425d362da 100644 --- a/packages/utils/.npmignore +++ b/packages/utils/.npmignore @@ -1,4 +1,6 @@ __snapshots__ __testfixtures__ +types *.test.* defineTest.* +*.ts diff --git a/packages/utils/ast-utils.js b/packages/utils/ast-utils.ts similarity index 61% rename from packages/utils/ast-utils.js rename to packages/utils/ast-utils.ts index 16af8aa9bbc..9d1fc58d50e 100644 --- a/packages/utils/ast-utils.js +++ b/packages/utils/ast-utils.ts @@ -1,4 +1,5 @@ -const validateIdentifier = require("./validate-identifier"); +import { IJSCodeshift, INode, valueType } from "./types/NodePath"; +import * as validateIdentifier from "./validate-identifier"; /** * @@ -7,9 +8,9 @@ const validateIdentifier = require("./validate-identifier"); * @param {Array} paths - Array of strings containing the traversal path * @returns {Any} Value at given traversal path */ -function safeTraverse(obj, paths) { - let val = obj; - let idx = 0; +function safeTraverse(obj: INode, paths: string[]): any { + let val: INode = obj; + let idx: number = 0; while (idx < paths.length) { if (!val) { @@ -27,33 +28,33 @@ function safeTraverse(obj, paths) { * @param {Node} path - AST node * @returns {String|Boolean} type at given path. */ -function safeTraverseAndGetType(path) { - const pathValue = safeTraverse(path, ["value", "value"]); +function safeTraverseAndGetType(path: INode): string | boolean { + const pathValue: INode = safeTraverse(path, ["value", "value"]); return pathValue ? pathValue.type : false; } // Convert nested MemberExpressions to strings like webpack.optimize.DedupePlugin -function memberExpressionToPathString(path) { +function memberExpressionToPathString(path: INode) { if (path && path.object) { return [memberExpressionToPathString(path.object), path.property.name].join( - "." + ".", ); } return path.name; } // Convert Array like ['webpack', 'optimize', 'DedupePlugin'] to nested MemberExpressions -function pathsToMemberExpression(j, paths) { +function pathsToMemberExpression(j: IJSCodeshift, paths: string[]): INode { if (!paths.length) { return null; } else if (paths.length === 1) { return j.identifier(paths[0]); } else { - const first = paths.slice(0, 1); - const rest = paths.slice(1); + const first: string[] = paths.slice(0, 1); + const rest: string[] = paths.slice(1); return j.memberExpression( pathsToMemberExpression(j, rest), - pathsToMemberExpression(j, first) + pathsToMemberExpression(j, first), ); } } @@ -69,12 +70,14 @@ function pathsToMemberExpression(j, paths) { * @returns {Node} Node that has the pluginName */ -function findPluginsByName(j, node, pluginNamesArray) { - return node.find(j.NewExpression).filter(path => { - return pluginNamesArray.some( - plugin => - memberExpressionToPathString(path.get("callee").value) === plugin - ); +function findPluginsByName(j: IJSCodeshift, node: INode, pluginNamesArray: string[]): INode { + return node + .find(j.NewExpression) + .filter((path: INode): boolean => { + return pluginNamesArray.some( + (plugin: string) => + memberExpressionToPathString(path.get("callee").value) === plugin, + ); }); } @@ -85,18 +88,20 @@ function findPluginsByName(j, node, pluginNamesArray) { * @returns {Node} rootNode modified AST. */ -function findPluginsArrayAndRemoveIfEmpty(j, rootNode) { - return rootNode.find(j.Identifier, { name: "plugins" }).forEach(node => { - const elements = safeTraverse(node, [ - "parent", - "value", - "value", - "elements" - ]); - if (!elements.length) { - j(node.parent).remove(); - } - }); +function findPluginsArrayAndRemoveIfEmpty(j: IJSCodeshift, rootNode: INode): INode { + return rootNode + .find(j.Identifier, { name: "plugins" }) + .forEach((node: INode) => { + const elements: INode[] = safeTraverse(node, [ + "parent", + "value", + "value", + "elements", + ]); + if (!elements.length) { + j(node.parent).remove(); + } + }); } /** @@ -109,7 +114,7 @@ function findPluginsArrayAndRemoveIfEmpty(j, rootNode) { * @returns {Node} found node and */ -function findRootNodesByName(j, node, propName) { +function findRootNodesByName(j: IJSCodeshift, node: INode, propName: string): INode { return node.find(j.Property, { key: { name: propName } }); } @@ -123,11 +128,11 @@ function findRootNodesByName(j, node, propName) { * @returns {Node} */ -function createProperty(j, key, value) { +function createProperty(j: IJSCodeshift, key: string | number, value: valueType): INode { return j.property( "init", createIdentifierOrLiteral(j, key), - createLiteral(j, value) + createLiteral(j, value), ); } @@ -140,16 +145,16 @@ function createProperty(j, key, value) { * @returns {Node} */ -function createLiteral(j, val) { - let literalVal = val; +function createLiteral(j: IJSCodeshift, val: valueType): INode { + let literalVal: valueType = val; // We'll need String to native type conversions if (typeof val === "string") { // 'true' => true - if (val === "true") literalVal = true; + if (val === "true") { literalVal = true; } // 'false' => false - if (val === "false") literalVal = false; + if (val === "false") { literalVal = false; } // '1' => 1 - if (!isNaN(Number(val))) literalVal = Number(val); + if (!isNaN(Number(val))) { literalVal = Number(val); } } return j.literal(literalVal); } @@ -163,37 +168,40 @@ function createLiteral(j, val) { * @returns {Node} */ -function createIdentifierOrLiteral(j, val) { +function createIdentifierOrLiteral(j: IJSCodeshift, val: valueType): INode { // IPath | IPath doesn't work, find another way let literalVal = val; // We'll need String to native type conversions - if (typeof val === "string" || val.__paths) { - // 'true' => true - if (val === "true") { - literalVal = true; - return j.literal(literalVal); - } - // 'false' => false - if (val === "false") { - literalVal = false; - return j.literal(literalVal); - } - // '1' => 1 - if (!isNaN(Number(val))) { - literalVal = Number(val); - return j.literal(literalVal); - } - if (val.__paths) { - let regExpVal = val.__paths[0].value.program.body[0].expression; - return j.literal(regExpVal.value); - } else { - // Use identifier instead - if ( - !validateIdentifier.isKeyword(literalVal) || - !validateIdentifier.isIdentifierStart(literalVal) || - !validateIdentifier.isIdentifierChar(literalVal) - ) - return j.identifier(literalVal); + if (!Array.isArray(val)) { + if (typeof val === "string" || typeof val === "object" && val.__paths) { + // 'true' => true + if (val === "true") { + literalVal = true; + return j.literal(literalVal); + } + // 'false' => false + if (val === "false") { + literalVal = false; + return j.literal(literalVal); + } + // '1' => 1 + if (!isNaN(Number(val))) { + literalVal = Number(val); + return j.literal(literalVal); + } + if (typeof val === "object" && val.__paths) { + const regExpVal = val.__paths[0].value.program.body[0].expression; + return j.literal(regExpVal.value); + } else if (typeof literalVal === "string") { + // Use identifier instead + if ( + !validateIdentifier.isKeyword(literalVal) || + !validateIdentifier.isIdentifierStart(literalVal) || + !validateIdentifier.isIdentifierChar(literalVal) + ) { + return j.identifier(literalVal); + } + } } } return j.literal(literalVal); @@ -212,27 +220,36 @@ function createIdentifierOrLiteral(j, val) { * @returns {Void} */ -function addOrUpdateConfigObject(j, rootNode, configProperty, key, value) { +function addOrUpdateConfigObject( + j: IJSCodeshift, rootNode: INode, configProperty: string, key: string, value: valueType, +): void { + const propertyExists = rootNode.properties.filter( - node => node.key.name === configProperty + (node: INode): boolean => node.key.name === configProperty, ).length; if (propertyExists) { rootNode.properties - .filter(path => path.key.name === configProperty) - .forEach(path => { + .filter((path: INode) => path.key.name === configProperty) + .forEach((path: INode) => { const newProperties = path.value.properties.filter( - path => path.key.name !== key + (p: INode) => p.key.name !== key, + ); + newProperties.push( + j.objectProperty( + j.identifier(key), value, + ), ); - newProperties.push(j.objectProperty(j.identifier(key), value)); path.value.properties = newProperties; }); } else { rootNode.properties.push( j.objectProperty( j.identifier(configProperty), - j.objectExpression([j.objectProperty(j.identifier(key), value)]) - ) + j.objectExpression( + [j.objectProperty(j.identifier(key), value)], + ), + ), ); } } @@ -248,14 +265,14 @@ function addOrUpdateConfigObject(j, rootNode, configProperty, key, value) { * @returns {Node | Void} - path to the root webpack configuration object if plugin is found */ -function findAndRemovePluginByName(j, node, pluginName) { - let rootPath; +function findAndRemovePluginByName(j: IJSCodeshift, node: INode, pluginName: string): INode { + let rootPath: INode; findPluginsByName(j, node, [pluginName]) - .filter(path => safeTraverse(path, ["parent", "value"])) - .forEach(path => { + .filter((path: INode) => safeTraverse(path, ["parent", "value"])) + .forEach((path: INode) => { rootPath = safeTraverse(path, ["parent", "parent", "parent", "value"]); - const arrayPath = path.parent.value; + const arrayPath: INode = path.parent.value; if (arrayPath.elements && arrayPath.elements.length === 1) { j(path.parent.parent).remove(); } else { @@ -272,46 +289,47 @@ function findAndRemovePluginByName(j, node, pluginName) { * If plugin declaration already exist, options are merged. * * @param {any} j — jscodeshift API - * @param {Node} rootNodePath - `plugins: []` NodePath where plugin should be added. See https://github.com/facebook/jscodeshift/wiki/jscodeshift-Documentation#nodepaths + * @param {Node} rootNodePath - `plugins: []` NodePath where plugin should be added. + * See https://github.com/facebook/jscodeshift/wiki/jscodeshift-Documentation#nodepaths * @param {String} pluginName - ex. `webpack.LoaderOptionsPlugin` * @param {Object} options - plugin options * @returns {Void} */ -function createOrUpdatePluginByName(j, rootNodePath, pluginName, options) { - const pluginInstancePath = findPluginsByName(j, j(rootNodePath), [ - pluginName +function createOrUpdatePluginByName(j: IJSCodeshift, rootNodePath: INode, pluginName: string, options: object): void { + const pluginInstancePath: INode = findPluginsByName(j, j(rootNodePath), [ + pluginName, ]); - let optionsProps; + let optionsProps: INode[]; if (options) { - optionsProps = Object.keys(options).map(key => { + optionsProps = Object.keys(options).map((key: string) => { return createProperty(j, key, options[key]); }); } // If plugin declaration already exist if (pluginInstancePath.size()) { - pluginInstancePath.forEach(path => { + pluginInstancePath.forEach((path: INode) => { // There are options we want to pass as argument if (optionsProps) { - const args = path.value.arguments; + const args: INode[] = path.value.arguments; if (args.length) { // Plugin is called with object as arguments // we will merge those objects - let currentProps = j(path) + const currentProps: INode = j(path) .find(j.ObjectExpression) .get("properties"); - optionsProps.forEach(opt => { + optionsProps.forEach((opt: INode) => { // Search for same keys in the existing object const existingProps = j(currentProps) .find(j.Identifier) - .filter(path => opt.key.value === path.value.name); + .filter((p: INode) => opt.key.value === p.value.name); if (existingProps.size()) { // Replacing values for the same key - existingProps.forEach(path => { - j(path.parent).replaceWith(opt); + existingProps.forEach((p: INode) => { + j(p.parent).replaceWith(opt); }); } else { // Adding new key:values @@ -325,13 +343,13 @@ function createOrUpdatePluginByName(j, rootNodePath, pluginName, options) { } }); } else { - let argumentsArray = []; + let argumentsArray: INode[] = []; if (optionsProps) { argumentsArray = [j.objectExpression(optionsProps)]; } const loaderPluginInstance = j.newExpression( pathsToMemberExpression(j, pluginName.split(".").reverse()), - argumentsArray + argumentsArray, ); rootNodePath.value.elements.push(loaderPluginInstance); } @@ -342,17 +360,18 @@ function createOrUpdatePluginByName(j, rootNodePath, pluginName, options) { * Finds the variable to which a third party plugin is assigned to * * @param {any} j — jscodeshift API - * @param {Node} rootNode - `plugins: []` Root Node. See https://github.com/facebook/jscodeshift/wiki/jscodeshift-Documentation#nodepaths + * @param {Node} rootNode - `plugins: []` Root Node. + * See https://github.com/facebook/jscodeshift/wiki/jscodeshift-Documentation#nodepaths * @param {String} pluginPackageName - ex. `extract-text-plugin` * @returns {String} variable name - ex. 'const s = require(s) gives "s"` */ -function findVariableToPlugin(j, rootNode, pluginPackageName) { - const moduleVarNames = rootNode +function findVariableToPlugin(j: IJSCodeshift, rootNode: INode, pluginPackageName: string): string { + const moduleVarNames: INode[] = rootNode .find(j.VariableDeclarator) .filter(j.filters.VariableDeclarator.requiresModule(pluginPackageName)) .nodes(); - if (moduleVarNames.length === 0) return null; + if (moduleVarNames.length === 0) { return null; } return moduleVarNames.pop().id.name; } @@ -364,13 +383,13 @@ function findVariableToPlugin(j, rootNode, pluginPackageName) { * @returns {Boolean} */ -function isType(path, type) { +function isType(path: INode, type: string): boolean { return path.type === type; } -function findObjWithOneOfKeys(p, keyNames) { - return p.value.properties.reduce((predicate, prop) => { - const name = prop.key.name; +function findObjWithOneOfKeys(p: INode, keyNames: string[]) { + return p.value.properties.reduce((predicate: boolean, prop: INode) => { + const name: string = prop.key.name; return keyNames.indexOf(name) > -1 || predicate; }, false); } @@ -384,12 +403,12 @@ function findObjWithOneOfKeys(p, keyNames) { * @returns {Node} - the created ast */ -function getRequire(j, constName, packagePath) { +function getRequire(j: IJSCodeshift, constName: string, packagePath: string): INode { return j.variableDeclaration("const", [ j.variableDeclarator( j.identifier(constName), - j.callExpression(j.identifier("require"), [j.literal(packagePath)]) - ) + j.callExpression(j.identifier("require"), [j.literal(packagePath)]), + ), ]); } @@ -404,17 +423,17 @@ function getRequire(j, constName, packagePath) { * @returns {Node} - the created ast */ -function addProperty(j, p, key, value, action) { +function addProperty(j: IJSCodeshift, p: INode, key: string, value: valueType, action?: string): INode { if (!p) { return; } - let valForNode; + let valForNode: valueType; if (Array.isArray(value)) { - let arrExp = j.arrayExpression([]); + let arrExp: INode = j.arrayExpression([]); if (safeTraverseAndGetType(p) === "ArrayExpression") { arrExp = p.value.value; } - value.forEach(val => { + value.forEach((val: valueType) => { addProperty(j, arrExp, null, val); }); valForNode = arrExp; @@ -422,19 +441,19 @@ function addProperty(j, p, key, value, action) { typeof value === "object" && !(value.__paths || value instanceof RegExp) ) { - let objectExp = j.objectExpression([]); + let objectExp: INode = j.objectExpression([]); if (safeTraverseAndGetType(p) === "ObjectExpression") { objectExp = p.value.value; } // object -> loop through it - Object.keys(value).forEach(prop => { + Object.keys(value).forEach((prop: string) => { addProperty(j, objectExp, prop, value[prop]); }); valForNode = objectExp; } else { valForNode = createIdentifierOrLiteral(j, value); } - let pushVal; + let pushVal: valueType; if (key) { pushVal = j.property("init", j.identifier(key), valForNode); } else { @@ -442,7 +461,7 @@ function addProperty(j, p, key, value, action) { } // we only return the generated pushVal which will be replace the node path - if (action === "add") return pushVal; + if (action === "add") { return pushVal; } if (p.properties) { p.properties.push(pushVal); @@ -459,6 +478,69 @@ function addProperty(j, p, key, value, action) { return; } +/** + * + * Removes an object/property from the config + * @param {any} j — jscodeshift API + * @param {Node} ast - AST node + * @param {String} key - key of a key/val object + * @param {Any} value - Any type of object + * @returns {Node} - the created ast + */ + +function removeProperty(j: IJSCodeshift, ast: INode, key: string, value: valueType): INode { + + if (typeof value === "object" && !Array.isArray(value)) { + // override for module.rules / loaders + if (key === "module" && value.rules) { + return ast + .find(j.Property, { + value: { + type: "Literal", + value: value.rules[0].loader, + }, + }) + .forEach((p: INode) => { + j(p.parent).remove(); + }); + } + } + + // value => array + if (Array.isArray(value)) { + return ast + .find(j.Literal, { + value: value[0], + }) + .forEach((p: INode) => { + const configKey = safeTraverse(p, ["parent", "parent", "node", "key", "name"]); + if (configKey === key) { + j(p).remove(); + } + }); + } + + // value => literal string / boolean / nested object + let objKeyToRemove: string | null = null; + if (value === null) { + objKeyToRemove = key; + } else if (typeof value === "object") { + for (const innerKey in value) { + if (value[innerKey] === null) { objKeyToRemove = innerKey; } + } + } + return ast + .find(j.Property, { + key: { + name: objKeyToRemove, + type: "Identifier", + }, + }) + .forEach((p: INode) => { + j(p).remove(); + }); +} + /** * * Get an property named topScope from yeoman and inject it to the top scope of @@ -471,9 +553,9 @@ function addProperty(j, p, key, value, action) { * @returns ast - jscodeshift API */ -function parseTopScope(j, ast, value, action) { - function createTopScopeProperty(p) { - value.forEach(n => { +function parseTopScope(j: IJSCodeshift, ast: INode, value: string[], action: string): boolean | INode { + function createTopScopeProperty(p: INode): boolean { + value.forEach((n: string) => { if ( !p.value.body[0].declarations || n.indexOf(p.value.body[0].declarations[0].id.name) <= 0 @@ -481,16 +563,15 @@ function parseTopScope(j, ast, value, action) { p.value.body.splice(-1, 0, n); } }); + return false; // TODO: debug later } if (value) { - return ast.find(j.Program).filter(p => createTopScopeProperty(p)); + return ast.find(j.Program).filter((p: INode): boolean => createTopScopeProperty(p)); } else { return ast; } } -("use strict"); - /** * * Transform for merge. Finds the merge property from yeoman and creates a way @@ -503,102 +584,42 @@ function parseTopScope(j, ast, value, action) { * @returns ast - jscodeshift API */ -function parseMerge(j, ast, value, action) { - function createMergeProperty(p) { +function parseMerge(j: IJSCodeshift, ast: INode, value: string, action: string): boolean | INode { + function createMergeProperty(p: INode) { // FIXME Use j.callExp() - let exportsDecl = p.value.body.map(n => { + const exportsDecl: INode[] = p.value.body.map((n: INode) => { if (n.expression) { return n.expression.right; } }); const bodyLength = exportsDecl.length; - let newVal = {}; + const newVal: INode = {}; newVal.type = "ExpressionStatement"; newVal.expression = { - type: "AssignmentExpression", - operator: "=", left: { - type: "MemberExpression", computed: false, object: j.identifier("module"), - property: j.identifier("exports") + property: j.identifier("exports"), + type: "MemberExpression", }, + operator: "=", right: j.callExpression(j.identifier("merge"), [ j.identifier(value), - exportsDecl.pop() - ]) + exportsDecl.pop(), + ]), + type: "AssignmentExpression", }; p.value.body[bodyLength - 1] = newVal; + return false; // TODO: debug later } if (value) { - return ast.find(j.Program).filter(p => createMergeProperty(p)); + return ast.find(j.Program).filter((p: INode): boolean => createMergeProperty(p)); } else { return ast; } } -/** - * - * Removes an object/property from the config - * @param {any} j — jscodeshift API - * @param {Node} ast - AST node - * @param {String} key - key of a key/val object - * @param {Any} value - Any type of object - * @returns {Node} - the created ast - */ - -function removeProperty(j, ast, key, value) { - - // override for module.rules / loaders - if (key === "module" && value.rules) { - return ast - .find(j.Property, { - value: { - type: "Literal", - value: value.rules[0].loader - } - }) - .forEach(p =>{ - j(p.parent).remove(); - }); - } - - // value => array - if (Array.isArray(value)) { - return ast - .find(j.Literal, { - value: value[0] - }) - .forEach(p =>{ - const configKey = safeTraverse(p, ["parent", "parent", "node", "key", "name"]); - if (configKey === key) { - j(p).remove(); - } - }); - } - - // value => literal string / boolean / nested object - let objKeyToRemove = null; - if (value === null) { - objKeyToRemove = key; - } else if (typeof value === "object") { - for (const innerKey in value) { - if (value[innerKey] === null) objKeyToRemove = innerKey; - } - } - return ast - .find(j.Property, { - key: { - type: "Identifier", - name: objKeyToRemove - }, - }) - .forEach(p => { - j(p).remove(); - }); -} - -module.exports = { +export { safeTraverse, safeTraverseAndGetType, createProperty, @@ -615,7 +636,7 @@ module.exports = { findObjWithOneOfKeys, getRequire, addProperty, + removeProperty, parseTopScope, parseMerge, - removeProperty }; diff --git a/packages/utils/copy-utils.js b/packages/utils/copy-utils.ts similarity index 58% rename from packages/utils/copy-utils.js rename to packages/utils/copy-utils.ts index 8a673925c51..301718bc92a 100644 --- a/packages/utils/copy-utils.js +++ b/packages/utils/copy-utils.ts @@ -1,4 +1,12 @@ -const path = require("path"); +import * as path from "path"; + +interface IGenerator { + fs: { + copy(from: string, to: string, options?: object): void; + copyTpl(from: string, to: string, context: object, templateOptions?: object, copyOptions?: object): void; + }; + destinationPath: (path: string) => string; +} /** * Takes in a file path in the `./templates` directory. Copies that @@ -8,18 +16,18 @@ const path = require("path"); * @param {string} templateDir Absolute path to template directory * @returns {Function} A curried function that takes a file path and copies it */ -const generatorCopy = ( - generator, - templateDir -) => /** @param {string} filePath */ filePath => { - const sourceParts = templateDir.split(path.delimiter); +export const generatorCopy = ( + generator: any, + templateDir: string, +): (filePath: string) => void => (filePath: string): void => { + const sourceParts: string[] = templateDir.split(path.delimiter); sourceParts.push.apply(sourceParts, filePath.split("/")); - const targetParts = path.dirname(filePath).split("/"); + const targetParts: string[] = path.dirname(filePath).split("/"); targetParts.push(path.basename(filePath, ".tpl")); generator.fs.copy( path.join.apply(null, sourceParts), - generator.destinationPath(path.join.apply(null, targetParts)) + generator.destinationPath(path.join.apply(null, targetParts)), ); }; @@ -34,24 +42,19 @@ const generatorCopy = ( * the template files. * @returns {Function} A curried function that takes a file path and copies it */ -const generatorCopyTpl = ( - generator, - templateDir, - templateData -) => /** @param {string} filePath */ filePath => { - const sourceParts = templateDir.split(path.delimiter); +export const generatorCopyTpl = ( + generator: any, + templateDir: string, + templateData: object, +): (filePath: string) => void => (filePath: string): void => { + const sourceParts: string[] = templateDir.split(path.delimiter); sourceParts.push.apply(sourceParts, filePath.split("/")); - const targetParts = path.dirname(filePath).split("/"); + const targetParts: string[] = path.dirname(filePath).split("/"); targetParts.push(path.basename(filePath, ".tpl").slice(1)); generator.fs.copyTpl( path.join.apply(null, sourceParts), generator.destinationPath(path.join.apply(null, targetParts)), - templateData + templateData, ); }; - -module.exports = { - generatorCopy, - generatorCopyTpl -}; diff --git a/packages/utils/defineTest.js b/packages/utils/defineTest.ts similarity index 76% rename from packages/utils/defineTest.js rename to packages/utils/defineTest.ts index 4186d2b18f4..1118cde9a32 100644 --- a/packages/utils/defineTest.js +++ b/packages/utils/defineTest.ts @@ -1,7 +1,26 @@ -"use strict"; +import * as fs from "fs"; +import * as path from "path"; +import { IJSCodeshift, INode } from "./types/NodePath"; -const fs = require("fs"); -const path = require("path"); +interface IModule { + ( + jscodeshift: IJSCodeshift, + ast: INode, + initOptions: string | boolean | object, + action: string, + transformName?: string, + ): INode; + default: transformType; + parser: string; +} + +type transformType = ( + jscodeshift: IJSCodeshift, + ast: INode, + initOptions: string | boolean | object, + action: string, + transformName?: string, +) => INode; /** * Utility function to run a jscodeshift script within a unit test. @@ -28,20 +47,20 @@ const path = require("path"); * @return {Function} Function that fires of the transforms */ function runSingleTransform( - dirName, - transformName, - testFilePrefix, - initOptions, - action -) { + dirName: string, + transformName: string, + testFilePrefix: string, + initOptions: object | boolean | string, + action: string, +): string { if (!testFilePrefix) { testFilePrefix = transformName; } - const fixtureDir = path.join(dirName, "__testfixtures__"); - const inputPath = path.join(fixtureDir, testFilePrefix + ".input.js"); - const source = fs.readFileSync(inputPath, "utf8"); + const fixtureDir: string = path.join(dirName, "__testfixtures__"); + const inputPath: string = path.join(fixtureDir, testFilePrefix + ".input.js"); + const source: string = fs.readFileSync(inputPath, "utf8"); - let module; + let module: IModule; // Assumes transform and test are on the same level if (action) { module = require(path.join(dirName, "recursive-parser" + ".js")); @@ -53,24 +72,24 @@ function runSingleTransform( // Jest resets the module registry after each test, so we need to always get // a fresh copy of jscodeshift on every test run. - let jscodeshift = require("jscodeshift/dist/core"); + let jscodeshift: IJSCodeshift = require("jscodeshift/dist/core"); if (module.parser) { jscodeshift = jscodeshift.withParser(module.parser); } - const ast = jscodeshift(source); + const ast: INode = jscodeshift(source); if (initOptions || typeof initOptions === "boolean") { return transform( jscodeshift, ast, initOptions, action, - transformName + transformName, ).toSource({ - quote: "single" + quote: "single", }); } return transform(jscodeshift, ast, source, action).toSource({ - quote: "single" + quote: "single", }); } @@ -91,14 +110,14 @@ function runSingleTransform( * @param {String} action init, update or remove, decides how to format the AST * @return {Void} Jest makes sure to execute the globally defined functions */ -function defineTest( - dirName, - transformName, - testFilePrefix, - transformObject, - action -) { - const testName = testFilePrefix +export default function defineTest( + dirName: string, + transformName: string, + testFilePrefix: string, + transformObject: object, + action: string, +): void { + const testName: string = testFilePrefix ? `transforms correctly using "${testFilePrefix}" data` : "transforms correctly"; describe(transformName, () => { @@ -108,10 +127,9 @@ function defineTest( transformName, testFilePrefix, transformObject, - action + action, ); expect(output).toMatchSnapshot(); }); }); } -module.exports = defineTest; diff --git a/packages/utils/index.js b/packages/utils/index.ts similarity index 100% rename from packages/utils/index.js rename to packages/utils/index.ts diff --git a/packages/utils/is-local-path.test.js b/packages/utils/is-local-path.test.js index c2be9ebc1a1..a6db609e694 100644 --- a/packages/utils/is-local-path.test.js +++ b/packages/utils/is-local-path.test.js @@ -1,6 +1,6 @@ "use strict"; -const isLocalPath = require("./is-local-path"); +const isLocalPath = require("./is-local-path").default; const path = require("path"); describe("is-local-path", () => { diff --git a/packages/utils/is-local-path.js b/packages/utils/is-local-path.ts similarity index 84% rename from packages/utils/is-local-path.js rename to packages/utils/is-local-path.ts index af17d590594..0efb8c6fc2c 100644 --- a/packages/utils/is-local-path.js +++ b/packages/utils/is-local-path.ts @@ -1,7 +1,5 @@ -"use strict"; - -const fs = require("fs"); -const path = require("path"); +import * as fs from "fs"; +import * as path from "path"; /** * Attempts to detect whether the string is a local path regardless of its @@ -14,6 +12,6 @@ const path = require("path"); * @returns {Boolean} whether the string could be a path to a local file or directory */ -module.exports = function(str) { +export default function(str: string): boolean { return path.isAbsolute(str) || /^\./.test(str) || fs.existsSync(str); -}; +} diff --git a/packages/utils/modify-config-helper.js b/packages/utils/modify-config-helper.js deleted file mode 100644 index f1749321729..00000000000 --- a/packages/utils/modify-config-helper.js +++ /dev/null @@ -1,104 +0,0 @@ -"use strict"; - -const fs = require("fs"); -const path = require("path"); -const chalk = require("chalk"); -const yeoman = require("yeoman-environment"); -const Generator = require("yeoman-generator"); -const logSymbols = require("log-symbols"); -const runTransform = require("./scaffold"); - -const DEFAULT_WEBPACK_CONFIG_FILENAME = "webpack.config.js"; - -/** - * - * Looks up the webpack.config in the user's path and runs a given - * generator scaffold followed up by a transform - * - * @param {String} action — action to be done (add, remove, update, init) - * @param {Class} generator - Yeoman generator class - * @param {String} configFile - Name of the existing/default webpack configuration file - * @param {Array} packages - List of packages to resolve - * @returns {Function} runTransform - Returns a transformation instance - */ - -module.exports = function modifyHelperUtil(action, generator, configFile = DEFAULT_WEBPACK_CONFIG_FILENAME, packages) { - let configPath = null; - - if (action !== "init") { - configPath = path.resolve(process.cwd(), configFile); - const webpackConfigExists = fs.existsSync(configPath); - if (webpackConfigExists) { - process.stdout.write( - "\n" + - logSymbols.success + - chalk.green(" SUCCESS ") + - "Found config " + - chalk.cyan(configFile + "\n") + - "\n" - ); - } else { - process.stdout.write( - "\n" + - logSymbols.error + - chalk.red(" ERROR ") + - chalk.cyan(configFile) + - " not found. Please specify a valid path to your webpack config like " + - chalk.white("$ ") + - chalk.cyan(`webpack-cli ${action} webpack.dev.js`) + - "\n" - ); - return; - } - } - - const env = yeoman.createEnv("webpack", null); - const generatorName = `webpack-${action}-generator`; - - if (!generator) { - generator = class extends Generator { - initializing() { - packages.forEach(pkgPath => { - return this.composeWith(require.resolve(pkgPath)); - }); - } - }; - } - env.registerStub(generator, generatorName); - - env.run(generatorName).on("end", () => { - let configModule; - try { - const configPath = path.resolve(process.cwd(), ".yo-rc.json"); - configModule = require(configPath); - // Change structure of the config to be transformed - let tmpConfig = {}; - Object.keys(configModule).forEach(prop => { - const configs = Object.keys(configModule[prop].configuration); - configs.forEach(config => { - tmpConfig[config] = configModule[prop].configuration[config]; - }); - }); - configModule = tmpConfig; - } catch (err) { - console.error( - chalk.red("\nCould not find a yeoman configuration file.\n") - ); - console.error( - chalk.red( - "\nPlease make sure to use 'this.config.set('configuration', this.configuration);' at the end of the generator.\n" - ) - ); - Error.stackTraceLimit = 0; - process.exitCode = -1; - } - const config = Object.assign( - { - configFile: !configPath ? null : fs.readFileSync(configPath, "utf8"), - configPath: configPath - }, - configModule - ); - return runTransform(config, action); - }); -}; diff --git a/packages/utils/modify-config-helper.ts b/packages/utils/modify-config-helper.ts new file mode 100644 index 00000000000..a8d40aa1a0f --- /dev/null +++ b/packages/utils/modify-config-helper.ts @@ -0,0 +1,126 @@ +import chalk from "chalk"; +import * as fs from "fs"; +import * as logSymbols from "log-symbols"; +import * as path from "path"; +import * as yeoman from "yeoman-environment"; +import Generator from "yeoman-generator"; + +import runTransform from "./scaffold"; +import { IGenerator, IYeoman } from "./types/Yeoman"; + +export interface IConfig extends Object { + item?: { + name: string; + }; + topScope?: string[]; + configName?: string; + merge: object; + webpackOptions: object; +} + +export interface ITransformConfig extends Object { + configPath?: string; + configFile?: string; + config?: IConfig; +} + +const DEFAULT_WEBPACK_CONFIG_FILENAME = "webpack.config.js"; + +/** + * + * Looks up the webpack.config in the user's path and runs a given + * generator scaffold followed up by a transform + * + * @param {String} action — action to be done (add, remove, update, init) + * @param {Class} generator - Yeoman generator class + * @param {String} configFile - Name of the existing/default webpack configuration file + * @param {Array} packages - List of packages to resolve + * @returns {Function} runTransform - Returns a transformation instance + */ + +export default function modifyHelperUtil( + action: string, + generator: IGenerator, + configFile: string = DEFAULT_WEBPACK_CONFIG_FILENAME, + packages?: string[]) + : Function { + + let configPath: string | null = null; + + if (action !== "init") { + configPath = path.resolve(process.cwd(), configFile); + const webpackConfigExists: boolean = fs.existsSync(configPath); + if (webpackConfigExists) { + process.stdout.write( + "\n" + + logSymbols.success + + chalk.green(" SUCCESS ") + + "Found config " + + chalk.cyan(configFile + "\n") + + "\n", + ); + } else { + process.stdout.write( + "\n" + + logSymbols.error + + chalk.red(" ERROR ") + + chalk.cyan(configFile) + + " not found. Please specify a valid path to your webpack config like " + + chalk.white("$ ") + + chalk.cyan(`webpack-cli ${action} webpack.dev.js`) + + "\n", + ); + return; + } + } + + const env: IYeoman = yeoman.createEnv("webpack", null); + const generatorName: string = `webpack-${action}-generator`; + + if (!generator) { + generator = class extends Generator { + public initializing() { + packages.forEach((pkgPath: string) => { + return (this as IGenerator).composeWith(require.resolve(pkgPath)); + }); + } + }; + } + env.registerStub(generator, generatorName); + + env.run(generatorName).on("end", (_: void) => { + let configModule: object; + try { + const confPath: string = path.resolve(process.cwd(), ".yo-rc.json"); + configModule = require(confPath); + // Change structure of the config to be transformed + const tmpConfig: object = {}; + Object.keys(configModule).forEach((prop: string): void => { + const configs: string[] = Object.keys(configModule[prop].configuration); + configs.forEach((conf: string): void => { + tmpConfig[conf] = configModule[prop].configuration[conf]; + }); + }); + configModule = tmpConfig; + } catch (err) { + console.error( + chalk.red("\nCould not find a yeoman configuration file.\n"), + ); + console.error( + chalk.red( + "\nPlease make sure to use 'this.config.set('configuration', this.configuration);' at the end of the generator.\n", + ), + ); + Error.stackTraceLimit = 0; + process.exitCode = -1; + } + const transformConfig: ITransformConfig = Object.assign( + { + configFile: !configPath ? null : fs.readFileSync(configPath, "utf8"), + configPath, + }, + configModule, + ); + return runTransform(transformConfig, action); + }); +} diff --git a/packages/utils/npm-exists.js b/packages/utils/npm-exists.js deleted file mode 100644 index 8283cce050a..00000000000 --- a/packages/utils/npm-exists.js +++ /dev/null @@ -1,23 +0,0 @@ -"use strict"; - -const got = require("got"); -const constant = value => _ => value; - -/** - * - * Checks if the given dependency/module is registered on npm - * - * @param {String} moduleName - The dependency to be checked - * @returns {Promise} constant - Returns either true or false, - * based on if it exists or not - */ - -module.exports = function npmExists(moduleName) { - const hostname = "https://www.npmjs.org"; - const pkgUrl = `${hostname}/package/${moduleName}`; - return got(pkgUrl, { - method: "HEAD" - }) - .then(constant(true)) - .catch(constant(false)); -}; diff --git a/packages/utils/npm-exists.test.js b/packages/utils/npm-exists.test.js index 00757ad7c0c..ebcf6dc9e21 100644 --- a/packages/utils/npm-exists.test.js +++ b/packages/utils/npm-exists.test.js @@ -1,5 +1,5 @@ "use strict"; -const exists = require("./npm-exists"); +const exists = require("./npm-exists").default; describe("npm-exists", () => { it("should successfully existence of a published module", () => { diff --git a/packages/utils/npm-exists.ts b/packages/utils/npm-exists.ts new file mode 100644 index 00000000000..2856f439e96 --- /dev/null +++ b/packages/utils/npm-exists.ts @@ -0,0 +1,22 @@ +import * as got from "got"; + +const constant = (value: boolean) => (res: got.Response): boolean | PromiseLike => value; + +/** + * + * Checks if the given dependency/module is registered on npm + * + * @param {String} moduleName - The dependency to be checked + * @returns {Promise} constant - Returns either true or false, + * based on if it exists or not + */ + +export default function npmExists(moduleName: string): Promise { + const hostname: string = "https://www.npmjs.org"; + const pkgUrl: string = `${hostname}/package/${moduleName}`; + return got(pkgUrl, { + method: "HEAD", + }) + .then(constant(true)) + .catch(constant(false)); +} diff --git a/packages/utils/npm-packages-exists.test.js b/packages/utils/npm-packages-exists.test.js index 3944047524e..e74e8815d87 100644 --- a/packages/utils/npm-packages-exists.test.js +++ b/packages/utils/npm-packages-exists.test.js @@ -1,4 +1,5 @@ -const npmPackagesExists = require("./npm-packages-exists"); +const npmPackagesExists = require("./npm-packages-exists").default; +// console.log('npmPackagesExists: ', npmPackagesExists); jest.mock("./npm-exists"); jest.mock("./resolve-packages"); @@ -22,7 +23,7 @@ describe("npmPackagesExists", () => { }); test("resolves packages when they are available on npm", done => { - require("./npm-exists").mockImplementation(() => Promise.resolve(true)); + require("./npm-exists").default.mockImplementation(() => Promise.resolve(true)); npmPackagesExists(["webpack-scaffold-foobar"]); setTimeout(() => { expect( diff --git a/packages/utils/npm-packages-exists.js b/packages/utils/npm-packages-exists.ts similarity index 70% rename from packages/utils/npm-packages-exists.js rename to packages/utils/npm-packages-exists.ts index 054674140fe..9cfd6e66ca5 100644 --- a/packages/utils/npm-packages-exists.js +++ b/packages/utils/npm-packages-exists.ts @@ -1,10 +1,10 @@ -"use strict"; -const chalk = require("chalk"); -const isLocalPath = require("./is-local-path"); -const npmExists = require("./npm-exists"); -const resolvePackages = require("./resolve-packages").resolvePackages; +import chalk from "chalk"; -const WEBPACK_SCAFFOLD_PREFIX = "webpack-scaffold"; +import isLocalPath from "./is-local-path"; +import npmExists from "./npm-exists"; +import { resolvePackages } from "./resolve-packages"; + +const WEBPACK_SCAFFOLD_PREFIX: string = "webpack-scaffold"; /** * @@ -15,15 +15,16 @@ const WEBPACK_SCAFFOLD_PREFIX = "webpack-scaffold"; * @returns {Array} resolvePackages - Returns an process to install the packages */ -module.exports = function npmPackagesExists(pkg) { - let acceptedPackages = []; +export default function npmPackagesExists(pkg: string[]): void { + const acceptedPackages: string[] = []; function resolvePackagesIfReady() { - if (acceptedPackages.length === pkg.length) + if (acceptedPackages.length === pkg.length) { return resolvePackages(acceptedPackages); + } } - pkg.forEach(addon => { + pkg.forEach((addon: string): void => { if (isLocalPath(addon)) { // If the addon is a path to a local folder, no name validation is necessary. acceptedPackages.push(addon); @@ -39,13 +40,13 @@ module.exports = function npmPackagesExists(pkg) { throw new TypeError( chalk.bold(`${addon} isn't a valid name.\n`) + chalk.red( - `\nIt should be prefixed with '${WEBPACK_SCAFFOLD_PREFIX}', but have different suffix.\n` - ) + `\nIt should be prefixed with '${WEBPACK_SCAFFOLD_PREFIX}', but have different suffix.\n`, + ), ); } npmExists(addon) - .then(moduleExists => { + .then((moduleExists: boolean) => { if (!moduleExists) { Error.stackTraceLimit = 0; throw new TypeError(`Cannot resolve location of package ${addon}.`); @@ -54,10 +55,10 @@ module.exports = function npmPackagesExists(pkg) { acceptedPackages.push(addon); } }) - .catch(err => { + .catch((err: IError) => { console.error(err.stack || err); process.exit(0); }) .then(resolvePackagesIfReady); }); -}; +} diff --git a/packages/utils/package-lock.json b/packages/utils/package-lock.json index 15ca2dd6171..533959ad8a1 100644 --- a/packages/utils/package-lock.json +++ b/packages/utils/package-lock.json @@ -39,6 +39,55 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" }, + "@types/chalk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-2.2.0.tgz", + "integrity": "sha512-1zzPV9FDe1I/WHhRkf9SNgqtRJWZqrBWgu7JGveuHmmyR9CnAPCie2N/x+iHrgnpYBIcCJWHBoMRv2TRWktsvw==", + "requires": { + "chalk": "*" + } + }, + "@types/cross-spawn": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.0.tgz", + "integrity": "sha512-evp2ZGsFw9YKprDbg8ySgC9NA15g3YgiI8ANkGmKKvvi0P2aDGYLPxQIC5qfeKNUOe3TjABVGuah6omPRpIYhg==", + "requires": { + "@types/node": "*" + } + }, + "@types/got": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@types/got/-/got-8.3.3.tgz", + "integrity": "sha512-OXZz0iG3oX9RHTZAeVGJQCjCOGx0r7MnSWebgUoX5CiFq0aVcRJrUPLQo/dBdKOaosbp4K9NTZTSec2zWk9YIg==", + "requires": { + "@types/node": "*" + } + }, + "@types/jest": { + "version": "23.1.6", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.1.6.tgz", + "integrity": "sha512-lBu2tjrfGuj0gARErNmHZJrnWBdRrYk2XqlBY3LRv8Dqxk3w3461uuFMKmwfDDiOa5kzXocUnunCBBacGwF3+A==" + }, + "@types/log-symbols": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/log-symbols/-/log-symbols-2.0.0.tgz", + "integrity": "sha512-YJhbp0sz3egFFKl3BcCNPQKzuGFOP4PACcsifhK6ROGnJUW9ViYLuLybQ9GQZm7Zejy3tkGuiXYMq3GiyGkU4g==" + }, + "@types/node": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.5.2.tgz", + "integrity": "sha512-m9zXmifkZsMHZBOyxZWilMwmTlpC8x5Ty360JKTiXvlXZfBWYpsg9ZZvP/Ye+iZUh+Q+MxDLjItVTWIsfwz+8Q==" + }, + "@types/p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha512-bqQAn+tAs1QwGQYNIbv8a0XT8Pzl6Z+6SVpca+vJngcvwRwws7eJj9P2rTJDpjwOSyX1iRNSlIokUlusV0mP0A==" + }, + "@types/prettier": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.13.1.tgz", + "integrity": "sha512-OlcCdqLtMvl+Hq4UkAxxppKX252NXsBm6RyJZVuBZtkduu3Dl8pdx78XS4K7oPGPOxpD6T+KzK0DV11G8ykTkw==" + }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -6775,6 +6824,11 @@ "prelude-ls": "~1.1.2" } }, + "typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==" + }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", diff --git a/packages/utils/package-manager.js b/packages/utils/package-manager.ts similarity index 60% rename from packages/utils/package-manager.js rename to packages/utils/package-manager.ts index cb67afa9539..f45f6bcfe8c 100644 --- a/packages/utils/package-manager.js +++ b/packages/utils/package-manager.ts @@ -1,12 +1,17 @@ -"use strict"; +import { SpawnSyncReturns } from "child_process"; -const path = require("path"); -const fs = require("fs"); -const spawn = require("cross-spawn"); +import * as spawn from "cross-spawn"; +import * as fs from "fs"; +import * as path from "path"; -const SPAWN_FUNCTIONS = { +interface ISpawnFunctions { + npm: (pkg: string, isNew: boolean) => SpawnSyncReturns; + yarn: (pkg: string, isNew: boolean) => SpawnSyncReturns; +} + +const SPAWN_FUNCTIONS: ISpawnFunctions = { npm: spawnNPM, - yarn: spawnYarn + yarn: spawnYarn, }; /** @@ -18,9 +23,9 @@ const SPAWN_FUNCTIONS = { * @returns {Function} spawn - Installs the package */ -function spawnNPM(pkg, isNew) { +function spawnNPM(pkg: string, isNew: boolean): SpawnSyncReturns { return spawn.sync("npm", [isNew ? "install" : "update", "-g", pkg], { - stdio: "inherit" + stdio: "inherit", }); } @@ -33,9 +38,9 @@ function spawnNPM(pkg, isNew) { * @returns {Function} spawn - Installs the package */ -function spawnYarn(pkg, isNew) { +function spawnYarn(pkg: string, isNew: boolean): SpawnSyncReturns { return spawn.sync("yarn", ["global", isNew ? "add" : "upgrade", pkg], { - stdio: "inherit" + stdio: "inherit", }); } /** @@ -46,11 +51,11 @@ function spawnYarn(pkg, isNew) { * @returns {Function} spawn - Installs the package */ -function spawnChild(pkg) { - const rootPath = getPathToGlobalPackages(); - const pkgPath = path.resolve(rootPath, pkg); - const packageManager = getPackageManager(); - const isNew = !fs.existsSync(pkgPath); +export function spawnChild(pkg: string): SpawnSyncReturns { + const rootPath: string = getPathToGlobalPackages(); + const pkgPath: string = path.resolve(rootPath, pkg); + const packageManager: string = getPackageManager(); + const isNew: boolean = !fs.existsSync(pkgPath); return SPAWN_FUNCTIONS[packageManager](pkg, isNew); } @@ -63,11 +68,11 @@ function spawnChild(pkg) { * @returns {String} - The package manager name */ -function getPackageManager() { - const hasLocalNPM = fs.existsSync( - path.resolve(process.cwd(), "package-lock.json") +export function getPackageManager(): string { + const hasLocalNPM: boolean = fs.existsSync( + path.resolve(process.cwd(), "package-lock.json"), ); - const hasLocalYarn = fs.existsSync(path.resolve(process.cwd(), "yarn.lock")); + const hasLocalYarn: boolean = fs.existsSync(path.resolve(process.cwd(), "yarn.lock")); if (hasLocalNPM) { return "npm"; } else if (hasLocalYarn) { @@ -87,8 +92,8 @@ function getPackageManager() { * * @returns {String} path - Path to global node_modules folder */ -function getPathToGlobalPackages() { - const manager = getPackageManager(); +export function getPathToGlobalPackages(): string { + const manager: string = getPackageManager(); if (manager === "yarn") { try { @@ -104,9 +109,3 @@ function getPathToGlobalPackages() { return require("global-modules"); } - -module.exports = { - getPackageManager, - getPathToGlobalPackages, - spawnChild -}; diff --git a/packages/utils/package.json b/packages/utils/package.json index cc3addb5af9..ededc8d2165 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -3,9 +3,6 @@ "version": "0.0.8", "description": "webpack-cli utility files", "main": "index.js", - "scripts": { - "test": "jest --detectOpenHandles" - }, "publishConfig": { "access": "public" }, @@ -23,5 +20,20 @@ "prettier": "^1.12.1", "yeoman-environment": "^2.1.1", "yeoman-generator": "^2.0.5" + }, + "devDependencies": { + "@types/chalk": "^2.2.0", + "@types/cross-spawn": "^6.0.0", + "@types/got": "^8.3.1", + "@types/jest": "^23.1.3", + "@types/log-symbols": "^2.0.0", + "@types/node": "^10.3.5", + "@types/p-each-series": "^1.0.0", + "@types/prettier": "^1.13.1", + "typescript": "^2.9.2" + }, + "scripts": { + "build": "tsc", + "test": "jest --detectOpenHandles" } } diff --git a/packages/utils/prop-types.js b/packages/utils/prop-types.ts similarity index 85% rename from packages/utils/prop-types.js rename to packages/utils/prop-types.ts index d7f9bfdd143..894ec0e3c47 100644 --- a/packages/utils/prop-types.js +++ b/packages/utils/prop-types.ts @@ -5,7 +5,7 @@ * @returns {Set} A new set with accepted webpack properties */ -module.exports = new Set([ +const PROP_TYPES: Set = new Set([ "amd", "bail", "cache", @@ -18,8 +18,8 @@ module.exports = new Set([ "mode", "module", "node", - "output", "optimization", + "output", "parallelism", "performance", "plugins", @@ -29,10 +29,12 @@ module.exports = new Set([ "recordsPath", "resolve", "resolveLoader", - "stats", "splitChunks", + "stats", "target", + "topScope", "watch", "watchOptions", - "topScope" ]); + +export default PROP_TYPES; diff --git a/packages/utils/recursive-parser.js b/packages/utils/recursive-parser.js deleted file mode 100644 index e52a34cdf28..00000000000 --- a/packages/utils/recursive-parser.js +++ /dev/null @@ -1,54 +0,0 @@ -"use strict"; - -const utils = require("./ast-utils"); - -module.exports = function recursiveTransform(j, ast, key, value, action) { - if (key === "topScope") { - return utils.parseTopScope(j, ast, value, action); - } else if (key === "merge") { - return utils.parseMerge(j, ast, value, action); - } - const node = utils.findRootNodesByName(j, ast, key); - - // get module.exports prop - const root = ast - .find(j.ObjectExpression) - .filter(p => { - return ( - utils.safeTraverse(p, [ - "parentPath", - "value", - "left", - "object", - "name" - ]) === "module" && - utils.safeTraverse(p, [ - "parentPath", - "value", - "left", - "property", - "name" - ]) === "exports" - ); - }) - .filter(p => p.value.properties); - - if (node.size() !== 0) { - if (action === "add") { - return utils.findRootNodesByName(j, root, key) - .forEach(p => { - // update property/replace - j(p).replaceWith(utils.addProperty(j, p, key, value, action)); - }); - } else if (action === "remove") { - return utils.removeProperty(j, root, key, value); - } - } else { - return root.forEach(p => { - if (value) { - // init, add new property - utils.addProperty(j, p, key, value); - } - }); - } -}; diff --git a/packages/utils/recursive-parser.test.js b/packages/utils/recursive-parser.test.js index 9b263d9a871..569f54404ae 100644 --- a/packages/utils/recursive-parser.test.js +++ b/packages/utils/recursive-parser.test.js @@ -1,6 +1,6 @@ "use strict"; -const defineTest = require("./defineTest"); +const defineTest = require("./defineTest").default; defineTest( diff --git a/packages/utils/recursive-parser.ts b/packages/utils/recursive-parser.ts new file mode 100644 index 00000000000..1c6d1f2a060 --- /dev/null +++ b/packages/utils/recursive-parser.ts @@ -0,0 +1,60 @@ +import * as utils from "./ast-utils"; +import { IJSCodeshift, INode, valueType } from "./types/NodePath"; + +export default function recursiveTransform( + j: IJSCodeshift, ast: INode, key: string, value: valueType, action: string, +): boolean | INode { + if (key === "topScope") { + if (Array.isArray(value)) { + return utils.parseTopScope(j, ast, value, action); + } + console.log("Error in parsing top scope, Array required"); + return false; + } else if (key === "merge") { + if (typeof value === "string") { + return utils.parseMerge(j, ast, value, action); + } + } + const node: INode = utils.findRootNodesByName(j, ast, key); + + // get module.exports prop + const root = ast + .find(j.ObjectExpression) + .filter((p: INode): boolean => { + return ( + utils.safeTraverse(p, [ + "parentPath", + "value", + "left", + "object", + "name", + ]) === "module" && + utils.safeTraverse(p, [ + "parentPath", + "value", + "left", + "property", + "name", + ]) === "exports" + ); + }) + .filter((p: INode): boolean => p.value.properties); + + if (node.size() !== 0) { + if (action === "add") { + return utils.findRootNodesByName(j, root, key) + .forEach((p: INode) => { + j(p).replaceWith(utils.addProperty(j, p, key, value, action)); + }); + } else if (action === "remove") { + return utils.removeProperty(j, root, key, value); + } + } else { + return root.forEach((p: INode) => { + if (value) { + // init, add new property + utils.addProperty(j, p, key, value, null); + } + }); + } +} diff --git a/packages/utils/resolve-packages.js b/packages/utils/resolve-packages.ts similarity index 66% rename from packages/utils/resolve-packages.js rename to packages/utils/resolve-packages.ts index efb432faa96..1a1a3825d7d 100644 --- a/packages/utils/resolve-packages.js +++ b/packages/utils/resolve-packages.ts @@ -1,14 +1,14 @@ -"use strict"; +import chalk from "chalk"; +import * as path from "path"; -const path = require("path"); -const chalk = require("chalk"); +import isLocalPath from "./is-local-path"; +import modifyConfigHelper from "./modify-config-helper"; +import { getPathToGlobalPackages } from "./package-manager"; +import { spawnChild } from "./package-manager"; -const modifyConfigHelper = require("./modify-config-helper"); - -const getPathToGlobalPackages = require("./package-manager") - .getPathToGlobalPackages; -const isLocalPath = require("./is-local-path"); -const spawnChild = require("./package-manager").spawnChild; +interface IChildProcess { + status: number; +} /** * @@ -18,9 +18,8 @@ const spawnChild = require("./package-manager").spawnChild; * @returns {Promise} promise - Returns a promise to the installation */ -function processPromise(child) { - return new Promise(function(resolve, reject) { - //eslint-disable-line +export function processPromise(child: IChildProcess): Promise { + return new Promise((resolve: (_?: void) => void, reject: (_?: void) => void) => { if (child.status !== 0) { reject(); } else { @@ -38,20 +37,21 @@ function processPromise(child) { * a webpack configuration through yeoman or throws an error */ -function resolvePackages(pkg) { +export function resolvePackages(pkg: string[]): Function | void { Error.stackTraceLimit = 30; - let packageLocations = []; + const packageLocations: string[] = []; - function invokeGeneratorIfReady() { - if (packageLocations.length === pkg.length) + function invokeGeneratorIfReady(): Function { + if (packageLocations.length === pkg.length) { return modifyConfigHelper("init", null, null, packageLocations); + } } - pkg.forEach(addon => { + pkg.forEach((addon: string) => { // Resolve paths to modules on local filesystem if (isLocalPath(addon)) { - let absolutePath = addon; + let absolutePath: string = addon; try { absolutePath = path.resolve(process.cwd(), addon); @@ -70,9 +70,9 @@ function resolvePackages(pkg) { // Resolve modules on npm registry processPromise(spawnChild(addon)) - .then(_ => { + .then((_: void) => { try { - const globalPath = getPathToGlobalPackages(); + const globalPath: string = getPathToGlobalPackages(); packageLocations.push(path.resolve(globalPath, addon)); } catch (err) { console.log("Package wasn't validated correctly.."); @@ -82,7 +82,7 @@ function resolvePackages(pkg) { process.exitCode = 1; } }) - .catch(err => { + .catch((err: string) => { console.log("Package couldn't be installed, aborting.."); console.log("\nReason: \n"); console.error(chalk.bold.red(err)); @@ -91,8 +91,3 @@ function resolvePackages(pkg) { .then(invokeGeneratorIfReady); }); } - -module.exports = { - resolvePackages, - processPromise -}; diff --git a/packages/utils/run-prettier.js b/packages/utils/run-prettier.ts similarity index 61% rename from packages/utils/run-prettier.js rename to packages/utils/run-prettier.ts index 99e64003ced..6a071246537 100644 --- a/packages/utils/run-prettier.js +++ b/packages/utils/run-prettier.ts @@ -1,8 +1,6 @@ -"use strict"; - -const prettier = require("prettier"); -const fs = require("fs"); -const chalk = require("chalk"); +import chalk from "chalk"; +import * as fs from "fs"; +import * as prettier from "prettier"; /** * @@ -11,26 +9,26 @@ const chalk = require("chalk"); * @param {String} outputPath - Path to write the config to * @param {Node} source - AST to write at the given path * @param {Function} cb - executes a callback after execution if supplied - * @returns {Function} Writes a file at given location and prints messages accordingly + * @returns {Void} Writes a file at given location and prints messages accordingly */ -module.exports = function runPrettier(outputPath, source, cb) { - function validateConfig() { - let prettySource; - let error; +export default function runPrettier(outputPath: string, source: string, cb?: Function): void { + function validateConfig(): void | Function { + let prettySource: string; + let error: object; try { prettySource = prettier.format(source, { singleQuote: true, + tabWidth: 1, useTabs: true, - tabWidth: 1 }); } catch (err) { process.stdout.write( "\n" + chalk.yellow( `WARNING: Could not apply prettier to ${outputPath}` + - " due validation error, but the file has been created\n" - ) + " due validation error, but the file has been created\n", + ), ); prettySource = source; error = err; @@ -41,4 +39,4 @@ module.exports = function runPrettier(outputPath, source, cb) { return fs.writeFileSync(outputPath, prettySource, "utf8"); } return fs.writeFile(outputPath, source, "utf8", validateConfig); -}; +} diff --git a/packages/utils/scaffold.js b/packages/utils/scaffold.js deleted file mode 100644 index deb395a6142..00000000000 --- a/packages/utils/scaffold.js +++ /dev/null @@ -1,102 +0,0 @@ -"use strict"; - -const path = require("path"); -const j = require("jscodeshift"); -const chalk = require("chalk"); -const pEachSeries = require("p-each-series"); - -const runPrettier = require("./run-prettier"); -const astTransform = require("./recursive-parser"); -const propTypes = require("./prop-types"); - -/** - * - * Maps back transforms that needs to be run using the configuration - * provided. - * - * @param {Object} transformObject - An Object with all transformations - * @param {Object} config - Configuration to transform - * @returns {Object} - An Object with the transformations to be run - */ - -function mapOptionsToTransform(config) { - return Object.keys(config.webpackOptions).filter(k => propTypes.has(k)); -} - -/** - * - * Runs the transformations from an object we get from yeoman - * - * @param {Object} webpackProperties - Configuration to transform - * @param {String} action - Action to be done on the given ast - * @returns {Promise} - A promise that writes each transform, runs prettier - * and writes the file - */ - -module.exports = function runTransform(webpackProperties, action) { - // webpackOptions.name sent to nameTransform if match - const webpackConfig = Object.keys(webpackProperties).filter(p => { - return p !== "configFile" && p !== "configPath"; - }); - const initActionNotDefined = action && action !== "init" ? true : false; - - webpackConfig.forEach(scaffoldPiece => { - const config = webpackProperties[scaffoldPiece]; - const transformations = mapOptionsToTransform(config); - if (config.topScope) { - transformations.push("topScope"); - } - if (config.merge) { - transformations.push("merge"); - } - const ast = j( - initActionNotDefined - ? webpackProperties.configFile - : "module.exports = {}" - ); - const transformAction = action || null; - return pEachSeries(transformations, f => { - if (f === "merge" || f === "topScope") { - return astTransform(j, ast, f, config[f], transformAction); - } - return astTransform(j, ast, f, config.webpackOptions[f], transformAction); - }) - .then(_ => { - let configurationName; - if (!config.configName) { - configurationName = "webpack.config.js"; - } else { - configurationName = "webpack." + config.configName + ".js"; - } - - const outputPath = initActionNotDefined - ? webpackProperties.configPath - : path.join(process.cwd(), configurationName); - const source = ast.toSource({ - quote: "single" - }); - - runPrettier(outputPath, source); - }) - .catch(err => { - console.error(err.message ? err.message : err); - }); - }); - if (initActionNotDefined && webpackProperties.config.item) { - process.stdout.write( - "\n" + - chalk.green( - `Congratulations! ${ - webpackProperties.config.item - } has been ${action}ed!\n` - ) - ); - } else { - process.stdout.write( - "\n" + - chalk.green( - "Congratulations! Your new webpack configuration file has been created!\n" - ) - ); - } -}; diff --git a/packages/utils/scaffold.ts b/packages/utils/scaffold.ts new file mode 100644 index 00000000000..541815478d1 --- /dev/null +++ b/packages/utils/scaffold.ts @@ -0,0 +1,108 @@ +import chalk from "chalk"; +import * as j from "jscodeshift"; +import pEachSeries = require("p-each-series"); +import * as path from "path"; + +import { IConfig, ITransformConfig } from "./modify-config-helper"; +import propTypes from "./prop-types"; +import astTransform from "./recursive-parser"; +import runPrettier from "./run-prettier"; +import { INode } from "./types/NodePath"; + +/** + * + * Maps back transforms that needs to be run using the configuration + * provided. + * + * @param {Object} config - Configuration to transform + * @returns {Array} - An array with keys on which transformations need to be run + */ + +function mapOptionsToTransform(config: IConfig): string[] { + return Object.keys(config.webpackOptions).filter((k: string) => propTypes.has(k)); +} + +/** + * + * Runs the transformations from an object we get from yeoman + * + * @param {Object} transformConfig - Configuration to transform + * @param {String} action - Action to be done on the given ast + * @returns {Promise} - A promise that writes each transform, runs prettier + * and writes the file + */ + +export default function runTransform(transformConfig: ITransformConfig, action: string): void { + // webpackOptions.name sent to nameTransform if match + const webpackConfig = Object.keys(transformConfig).filter((p: string) => { + return p !== "configFile" && p !== "configPath"; + }); + const initActionNotDefined: boolean = action && action !== "init" ? true : false; + + webpackConfig.forEach((scaffoldPiece: string) => { + const config: IConfig = transformConfig[scaffoldPiece]; + + const transformations: string[] = mapOptionsToTransform(config); + + if (config.topScope) { + transformations.push("topScope"); + } + + if (config.merge) { + transformations.push("merge"); + } + + const ast: INode = j( + initActionNotDefined + ? transformConfig.configFile + : "module.exports = {}", + ); + + const transformAction: string = action || null; + + return pEachSeries(transformations, (f: string): boolean | INode => { + if (f === "merge" || f === "topScope") { + return astTransform(j, ast, f, config[f], transformAction); + } + return astTransform(j, ast, f, config.webpackOptions[f], transformAction); + }) + .then((_: any) => { + let configurationName: string; + if (!config.configName) { + configurationName = "webpack.config.js"; + } else { + configurationName = "webpack." + config.configName + ".js"; + } + + const outputPath: string = initActionNotDefined + ? transformConfig.configPath + : path.join(process.cwd(), configurationName); + const source: string = ast.toSource({ + quote: "single", + }); + + runPrettier(outputPath, source); + }) + .catch((err: IError) => { + console.error(err.message ? err.message : err); + }); + }); + + if (initActionNotDefined && transformConfig.config.item) { + process.stdout.write( + "\n" + + chalk.green( + `Congratulations! ${ + transformConfig.config.item + } has been ${action}ed!\n`, + ), + ); + } else { + process.stdout.write( + "\n" + + chalk.green( + "Congratulations! Your new webpack configuration file has been created!\n", + ), + ); + } +} diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json new file mode 100644 index 00000000000..4082f16a5d9 --- /dev/null +++ b/packages/utils/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/utils/types/NodePath.js b/packages/utils/types/NodePath.js deleted file mode 100644 index c8ad2e549bd..00000000000 --- a/packages/utils/types/NodePath.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/packages/utils/types/NodePath.ts b/packages/utils/types/NodePath.ts new file mode 100644 index 00000000000..d082ddc6587 --- /dev/null +++ b/packages/utils/types/NodePath.ts @@ -0,0 +1,93 @@ +export interface INode extends Object { + id?: { + name: string; + }; + arguments?: INode[]; + body?: INode[]; + elements?: INode[]; + expression?: { + left: { + computed: boolean, + object: INode, + property: INode, + type: string, + }, + operator: string, + right: INode, + type: string, + }; + filter?: (p: (p: INode) => boolean) => INode; + find?: (objectExpression: object, filterExpression?: object) => INode; + forEach?: (p: (p: INode) => void) => INode; + get?: (property: string) => INode; + remove?: (_?: void) => void; + nodes?: (_?: void) => INode[]; + pop?: (_?: void) => INode; + key?: { + name: string; + value: INode | string; + }; + node?: INode; + name?: string; + object?: object; + parent?: INode; + properties?: INode[]; + property?: INode; + prune?: Function; + replaceWith?: (objectExpression: object) => INode; + size?: (_?: void) => number; + type?: string; + value?: INode | string | any; + toSource?: (object: { + quote?: string, + }) => string; + source?: string; + ast?: INode; + rules?: IModuleRule[]; + __paths?: INode[]; +} + +interface IModuleRule { + loader?: string; +} + +interface IExpressionObject { + name?: string; +} + +export interface IJSCodeshift extends Object { + (source?: INode | string): INode; + withParser?: (parser: string) => IJSCodeshift; + identifier?: (key: string) => INode; + literal?: (key: valueType) => INode; + memberExpression?: (node1: INode, node2: INode, bool?: boolean) => INode; + objectProperty?: (key: INode, property: valueType) => INode; + objectExpression?: (properties: INode[]) => INode; + newExpression?: (expression: INode, args: INode[]) => INode; + callExpression?: (expression: INode, args: INode[]) => INode; + variableDeclarator?: (key: INode, args: INode) => INode; + variableDeclaration?: (key: string, args: INode[]) => INode; + arrayExpression?: (args?: INode[]) => INode; + property?: (type: string, key: INode, value: INode) => INode; + program?: (nodes: INode[]) => INode; + booleanLiteral?: (bool: boolean) => INode; + Property?: IExpressionObject; + NewExpression?: IExpressionObject; + CallExpression?: IExpressionObject; + VariableDeclarator?: IExpressionObject; + Identifier?: IExpressionObject; + Literal?: IExpressionObject; + ArrayExpression?: IExpressionObject; + MemberExpression?: IExpressionObject; + FunctionExpression?: IExpressionObject; + ObjectExpression?: IExpressionObject; + BlockStatement?: IExpressionObject; + Program?: IExpressionObject; + filters?: { + VariableDeclarator: { + requiresModule: Function, + }, + }; +} + +export type valueType = string | number | boolean | any[] | INode | null; diff --git a/packages/utils/types/Yeoman.js b/packages/utils/types/Yeoman.js deleted file mode 100644 index c8ad2e549bd..00000000000 --- a/packages/utils/types/Yeoman.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/packages/utils/types/Yeoman.ts b/packages/utils/types/Yeoman.ts new file mode 100644 index 00000000000..4c56a771c00 --- /dev/null +++ b/packages/utils/types/Yeoman.ts @@ -0,0 +1,12 @@ +interface IRunEnv extends Object { + on?: (event: string, callbackFn: Function) => void; +} + +export interface IYeoman extends Object { + registerStub?(generator: IGenerator, namespace: string): void; + run?(target: string, options?: object, done?: Function): IRunEnv; +} + +export interface IGenerator extends Object { + composeWith?: (path: string) => void; +} diff --git a/packages/utils/types/index.js b/packages/utils/types/index.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/utils/types/index.ts b/packages/utils/types/index.ts new file mode 100644 index 00000000000..fd94138d988 --- /dev/null +++ b/packages/utils/types/index.ts @@ -0,0 +1,4 @@ +interface IError { + stack?: object; + message?: string; +} diff --git a/packages/utils/validate-identifier.js b/packages/utils/validate-identifier.js deleted file mode 100644 index 67f3cb526fa..00000000000 --- a/packages/utils/validate-identifier.js +++ /dev/null @@ -1,613 +0,0 @@ -// This is a trick taken from Esprima. It turns out that, on -// non-Chrome browsers, to check whether a string is in a set, a -// predicate containing a big ugly `switch` statement is faster than -// a regular expression, and on Chrome the two are about on par. -// https://tc39.github.io/ecma262/#sec-keywords -function isKeyword(code) { - switch (code.length) { - case 2: - return code === "if" || code === "in" || code === "do"; - case 3: - return ( - code === "var" || - code === "for" || - code === "new" || - code === "try" || - code === "let" - ); - case 4: - return ( - code === "this" || - code === "case" || - code === "void" || - code === "with" || - code === "enum" || - code === "true" || - code === "null" || - code === "eval" - ); - case 5: - return ( - code === "while" || - code === "break" || - code === "catch" || - code === "throw" || - code === "const" || - code === "yield" || - code === "class" || - code === "super" || - code === "false" || - code === "await" - ); - case 6: - return ( - code === "return" || - code === "typeof" || - code === "delete" || - code === "switch" || - code === "export" || - code === "import" || - code === "static" || - code === "public" - ); - case 7: - return ( - code === "default" || - code === "finally" || - code === "extends" || - code === "private" || - code === "package" - ); - case 8: - return code === "function" || code === "continue" || code === "debugger"; - case 9: - return ( - code === "protected" || code === "interface" || code === "arguments" - ); - case 10: - return code === "instanceof" || code === "implements"; - default: - return false; - } -} -// ## Character categories - -// Big ugly regular expressions that match characters in the -// whitespace, identifier, and identifier-start categories. These -// are only applied when a character is found to actually have a -// code point above 128. - -let nonASCIIidentifierStartChars = - "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0-\u08b2\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua7ad\ua7b0\ua7b1\ua7f7-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab5f\uab64\uab65\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; -let nonASCIIidentifierChars = - "\u200c\u200d\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u08e4-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c03\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0d01-\u0d03\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19b0-\u19c0\u19c8\u19c9\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf2-\u1cf4\u1cf8\u1cf9\u1dc0-\u1df5\u1dfc-\u1dff\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua620-\ua629\ua66f\ua674-\ua67d\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua880\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8e0-\ua8f1\ua900-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2d\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; - -const nonASCIIidentifierStart = new RegExp( - "[" + nonASCIIidentifierStartChars + "]" -); -const nonASCIIidentifier = new RegExp( - "[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]" -); - -nonASCIIidentifierStartChars = nonASCIIidentifierChars = null; - -// These are a run-length and offset encoded representation of the -// >0xffff code points that are a valid part of identifiers. The -// offset starts at 0x10000, and each pair of numbers represents an -// offset to the next range, and then a size of the range. -let astralIdentifierStartCodes = [ - 0, - 11, - 2, - 25, - 2, - 18, - 2, - 1, - 2, - 14, - 3, - 13, - 35, - 122, - 70, - 52, - 268, - 28, - 4, - 48, - 48, - 31, - 17, - 26, - 6, - 37, - 11, - 29, - 3, - 35, - 5, - 7, - 2, - 4, - 43, - 157, - 99, - 39, - 9, - 51, - 157, - 310, - 10, - 21, - 11, - 7, - 153, - 5, - 3, - 0, - 2, - 43, - 2, - 1, - 4, - 0, - 3, - 22, - 11, - 22, - 10, - 30, - 98, - 21, - 11, - 25, - 71, - 55, - 7, - 1, - 65, - 0, - 16, - 3, - 2, - 2, - 2, - 26, - 45, - 28, - 4, - 28, - 36, - 7, - 2, - 27, - 28, - 53, - 11, - 21, - 11, - 18, - 14, - 17, - 111, - 72, - 955, - 52, - 76, - 44, - 33, - 24, - 27, - 35, - 42, - 34, - 4, - 0, - 13, - 47, - 15, - 3, - 22, - 0, - 38, - 17, - 2, - 24, - 133, - 46, - 39, - 7, - 3, - 1, - 3, - 21, - 2, - 6, - 2, - 1, - 2, - 4, - 4, - 0, - 32, - 4, - 287, - 47, - 21, - 1, - 2, - 0, - 185, - 46, - 82, - 47, - 21, - 0, - 60, - 42, - 502, - 63, - 32, - 0, - 449, - 56, - 1288, - 920, - 104, - 110, - 2962, - 1070, - 13266, - 568, - 8, - 30, - 114, - 29, - 19, - 47, - 17, - 3, - 32, - 20, - 6, - 18, - 881, - 68, - 12, - 0, - 67, - 12, - 16481, - 1, - 3071, - 106, - 6, - 12, - 4, - 8, - 8, - 9, - 5991, - 84, - 2, - 70, - 2, - 1, - 3, - 0, - 3, - 1, - 3, - 3, - 2, - 11, - 2, - 0, - 2, - 6, - 2, - 64, - 2, - 3, - 3, - 7, - 2, - 6, - 2, - 27, - 2, - 3, - 2, - 4, - 2, - 0, - 4, - 6, - 2, - 339, - 3, - 24, - 2, - 24, - 2, - 30, - 2, - 24, - 2, - 30, - 2, - 24, - 2, - 30, - 2, - 24, - 2, - 30, - 2, - 24, - 2, - 7, - 4149, - 196, - 1340, - 3, - 2, - 26, - 2, - 1, - 2, - 0, - 3, - 0, - 2, - 9, - 2, - 3, - 2, - 0, - 2, - 0, - 7, - 0, - 5, - 0, - 2, - 0, - 2, - 0, - 2, - 2, - 2, - 1, - 2, - 0, - 3, - 0, - 2, - 0, - 2, - 0, - 2, - 0, - 2, - 0, - 2, - 1, - 2, - 0, - 3, - 3, - 2, - 6, - 2, - 3, - 2, - 3, - 2, - 0, - 2, - 9, - 2, - 16, - 6, - 2, - 2, - 4, - 2, - 16, - 4421, - 42710, - 42, - 4148, - 12, - 221, - 16355, - 541 -]; -let astralIdentifierCodes = [ - 509, - 0, - 227, - 0, - 150, - 4, - 294, - 9, - 1368, - 2, - 2, - 1, - 6, - 3, - 41, - 2, - 5, - 0, - 166, - 1, - 1306, - 2, - 54, - 14, - 32, - 9, - 16, - 3, - 46, - 10, - 54, - 9, - 7, - 2, - 37, - 13, - 2, - 9, - 52, - 0, - 13, - 2, - 49, - 13, - 16, - 9, - 83, - 11, - 168, - 11, - 6, - 9, - 8, - 2, - 57, - 0, - 2, - 6, - 3, - 1, - 3, - 2, - 10, - 0, - 11, - 1, - 3, - 6, - 4, - 4, - 316, - 19, - 13, - 9, - 214, - 6, - 3, - 8, - 112, - 16, - 16, - 9, - 82, - 12, - 9, - 9, - 535, - 9, - 20855, - 9, - 135, - 4, - 60, - 6, - 26, - 9, - 1016, - 45, - 17, - 3, - 19723, - 1, - 5319, - 4, - 4, - 5, - 9, - 7, - 3, - 6, - 31, - 3, - 149, - 2, - 1418, - 49, - 4305, - 6, - 792618, - 239 -]; - -// This has a complexity linear to the value of the code. The -// assumption is that looking up astral identifier characters is -// rare. -function isInAstralSet(code, set) { - let pos = 0x10000; - for (let i = 0; i < set.length; i += 2) { - pos += set[i]; - if (pos > code) return false; - - pos += set[i + 1]; - if (pos >= code) return true; - } -} - -// Test whether a given character code starts an identifier. - -function isIdentifierStart(code) { - let c = code.charCodeAt(0); - if (c < 65) return c === 36; - if (c < 91) return true; - if (c < 97) return c === 95; - if (c < 123) return true; - if (c <= 0xffff) - return c >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(c)); - return isInAstralSet(c, astralIdentifierStartCodes); -} - -// Test whether a given character is part of an identifier. - -function isIdentifierChar(code) { - let l = code.length; - while (l--) { - let ch = code.charCodeAt(code.length - l); - return ( - validationChar(ch) || - ch === 0x24 /* $ */ || - ch === 0x5f /* _ */ || - ch === 0x200c /* */ || - ch === 0x200d - ); /* */ - } -} - -function validationChar(code) { - if (code < 48) return code === 36; - if (code < 58) return true; - if (code < 65) return false; - if (code < 91) return true; - if (code < 97) return code === 95; - if (code < 123) return true; - if (code <= 0xffff) - return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); - return ( - isInAstralSet(code, astralIdentifierStartCodes) || - isInAstralSet(code, astralIdentifierCodes) - ); -} - -module.exports = { - isIdentifierChar, - isIdentifierStart, - isKeyword -}; diff --git a/packages/utils/validate-identifier.ts b/packages/utils/validate-identifier.ts new file mode 100644 index 00000000000..341af6c6bd6 --- /dev/null +++ b/packages/utils/validate-identifier.ts @@ -0,0 +1,618 @@ +// This is a trick taken from Esprima. It turns out that, on +// non-Chrome browsers, to check whether a string is in a set, a +// predicate containing a big ugly `switch` statement is faster than +// a regular expression, and on Chrome the two are about on par. +// https://tc39.github.io/ecma262/#sec-keywords +function isKeyword(code: string) { + switch (code.length) { + case 2: + return code === "if" || code === "in" || code === "do"; + case 3: + return ( + code === "var" || + code === "for" || + code === "new" || + code === "try" || + code === "let" + ); + case 4: + return ( + code === "this" || + code === "case" || + code === "void" || + code === "with" || + code === "enum" || + code === "true" || + code === "null" || + code === "eval" + ); + case 5: + return ( + code === "while" || + code === "break" || + code === "catch" || + code === "throw" || + code === "const" || + code === "yield" || + code === "class" || + code === "super" || + code === "false" || + code === "await" + ); + case 6: + return ( + code === "return" || + code === "typeof" || + code === "delete" || + code === "switch" || + code === "export" || + code === "import" || + code === "static" || + code === "public" + ); + case 7: + return ( + code === "default" || + code === "finally" || + code === "extends" || + code === "private" || + code === "package" + ); + case 8: + return code === "function" || code === "continue" || code === "debugger"; + case 9: + return ( + code === "protected" || code === "interface" || code === "arguments" + ); + case 10: + return code === "instanceof" || code === "implements"; + default: + return false; + } +} +// ## Character categories + +// Big ugly regular expressions that match characters in the +// whitespace, identifier, and identifier-start categories. These +// are only applied when a character is found to actually have a +// code point above 128. + +/* tslint:disable: max-line-length */ +let nonASCIIidentifierStartChars: string = +"\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0-\u08b2\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua7ad\ua7b0\ua7b1\ua7f7-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab5f\uab64\uab65\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; +let nonASCIIidentifierChars = +"\u200c\u200d\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u08e4-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c03\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0d01-\u0d03\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19b0-\u19c0\u19c8\u19c9\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf2-\u1cf4\u1cf8\u1cf9\u1dc0-\u1df5\u1dfc-\u1dff\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua620-\ua629\ua66f\ua674-\ua67d\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua880\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8e0-\ua8f1\ua900-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2d\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; +/* tslint:enable: max-line-length */ + +const nonASCIIidentifierStart: RegExp = new RegExp( + "[" + nonASCIIidentifierStartChars + "]", +); +const nonASCIIidentifier: RegExp = new RegExp( + "[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]", +); + +nonASCIIidentifierStartChars = nonASCIIidentifierChars = null; + +// These are a run-length and offset encoded representation of the +// >0xffff code points that are a valid part of identifiers. The +// offset starts at 0x10000, and each pair of numbers represents an +// offset to the next range, and then a size of the range. +const astralIdentifierStartCodes: number[] = [ + 0, + 11, + 2, + 25, + 2, + 18, + 2, + 1, + 2, + 14, + 3, + 13, + 35, + 122, + 70, + 52, + 268, + 28, + 4, + 48, + 48, + 31, + 17, + 26, + 6, + 37, + 11, + 29, + 3, + 35, + 5, + 7, + 2, + 4, + 43, + 157, + 99, + 39, + 9, + 51, + 157, + 310, + 10, + 21, + 11, + 7, + 153, + 5, + 3, + 0, + 2, + 43, + 2, + 1, + 4, + 0, + 3, + 22, + 11, + 22, + 10, + 30, + 98, + 21, + 11, + 25, + 71, + 55, + 7, + 1, + 65, + 0, + 16, + 3, + 2, + 2, + 2, + 26, + 45, + 28, + 4, + 28, + 36, + 7, + 2, + 27, + 28, + 53, + 11, + 21, + 11, + 18, + 14, + 17, + 111, + 72, + 955, + 52, + 76, + 44, + 33, + 24, + 27, + 35, + 42, + 34, + 4, + 0, + 13, + 47, + 15, + 3, + 22, + 0, + 38, + 17, + 2, + 24, + 133, + 46, + 39, + 7, + 3, + 1, + 3, + 21, + 2, + 6, + 2, + 1, + 2, + 4, + 4, + 0, + 32, + 4, + 287, + 47, + 21, + 1, + 2, + 0, + 185, + 46, + 82, + 47, + 21, + 0, + 60, + 42, + 502, + 63, + 32, + 0, + 449, + 56, + 1288, + 920, + 104, + 110, + 2962, + 1070, + 13266, + 568, + 8, + 30, + 114, + 29, + 19, + 47, + 17, + 3, + 32, + 20, + 6, + 18, + 881, + 68, + 12, + 0, + 67, + 12, + 16481, + 1, + 3071, + 106, + 6, + 12, + 4, + 8, + 8, + 9, + 5991, + 84, + 2, + 70, + 2, + 1, + 3, + 0, + 3, + 1, + 3, + 3, + 2, + 11, + 2, + 0, + 2, + 6, + 2, + 64, + 2, + 3, + 3, + 7, + 2, + 6, + 2, + 27, + 2, + 3, + 2, + 4, + 2, + 0, + 4, + 6, + 2, + 339, + 3, + 24, + 2, + 24, + 2, + 30, + 2, + 24, + 2, + 30, + 2, + 24, + 2, + 30, + 2, + 24, + 2, + 30, + 2, + 24, + 2, + 7, + 4149, + 196, + 1340, + 3, + 2, + 26, + 2, + 1, + 2, + 0, + 3, + 0, + 2, + 9, + 2, + 3, + 2, + 0, + 2, + 0, + 7, + 0, + 5, + 0, + 2, + 0, + 2, + 0, + 2, + 2, + 2, + 1, + 2, + 0, + 3, + 0, + 2, + 0, + 2, + 0, + 2, + 0, + 2, + 0, + 2, + 1, + 2, + 0, + 3, + 3, + 2, + 6, + 2, + 3, + 2, + 3, + 2, + 0, + 2, + 9, + 2, + 16, + 6, + 2, + 2, + 4, + 2, + 16, + 4421, + 42710, + 42, + 4148, + 12, + 221, + 16355, + 541, +]; + +const astralIdentifierCodes: number[] = [ + 509, + 0, + 227, + 0, + 150, + 4, + 294, + 9, + 1368, + 2, + 2, + 1, + 6, + 3, + 41, + 2, + 5, + 0, + 166, + 1, + 1306, + 2, + 54, + 14, + 32, + 9, + 16, + 3, + 46, + 10, + 54, + 9, + 7, + 2, + 37, + 13, + 2, + 9, + 52, + 0, + 13, + 2, + 49, + 13, + 16, + 9, + 83, + 11, + 168, + 11, + 6, + 9, + 8, + 2, + 57, + 0, + 2, + 6, + 3, + 1, + 3, + 2, + 10, + 0, + 11, + 1, + 3, + 6, + 4, + 4, + 316, + 19, + 13, + 9, + 214, + 6, + 3, + 8, + 112, + 16, + 16, + 9, + 82, + 12, + 9, + 9, + 535, + 9, + 20855, + 9, + 135, + 4, + 60, + 6, + 26, + 9, + 1016, + 45, + 17, + 3, + 19723, + 1, + 5319, + 4, + 4, + 5, + 9, + 7, + 3, + 6, + 31, + 3, + 149, + 2, + 1418, + 49, + 4305, + 6, + 792618, + 239, +]; + +// This has a complexity linear to the value of the code. The +// assumption is that looking up astral identifier characters is +// rare. +function isInAstralSet(code: number, set: number[]): boolean { + let pos = 0x10000; + for (let i = 0; i < set.length; i += 2) { + pos += set[i]; + if (pos > code) { return false; } + + pos += set[i + 1]; + if (pos >= code) { return true; } + } +} + +// Test whether a given character code starts an identifier. + +function isIdentifierStart(code: string): boolean { + const c: number = code.charCodeAt(0); + if (c < 65) { return c === 36; } + if (c < 91) { return true; } + if (c < 97) { return c === 95; } + if (c < 123) { return true; } + if (c <= 0xffff) { + return c >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(c)); + } + return isInAstralSet(c, astralIdentifierStartCodes); +} + +// Test whether a given character is part of an identifier. + +function isIdentifierChar(code: string): boolean { + let l: number = code.length; + while (l--) { + const ch: number = code.charCodeAt(code.length - l); + return ( + validationChar(ch) || + ch === 0x24 /* $ */ || + ch === 0x5f /* _ */ || + ch === 0x200c /* */ || + ch === 0x200d + ); /* */ + } +} + +function validationChar(charCode: number): boolean { + if (charCode < 48) { return charCode === 36; } + if (charCode < 58) { return true; } + if (charCode < 65) { return false; } + if (charCode < 91) { return true; } + if (charCode < 97) { return charCode === 95; } + if (charCode < 123) { return true; } + if (charCode <= 0xffff) { + return charCode >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(charCode)); + } + return ( + isInAstralSet(charCode, astralIdentifierStartCodes) || + isInAstralSet(charCode, astralIdentifierCodes) + ); +} + +export { + isIdentifierChar, + isIdentifierStart, + isKeyword, +};