From 3936ca6ddb55d52957c440f3502a51f32d7abeaf Mon Sep 17 00:00:00 2001 From: "William C. Johnson" Date: Fri, 6 Oct 2017 23:54:56 -0400 Subject: [PATCH] Compiler config cleanup - eliminated hack checking for test fixture in file path - Can pass `isLightScript: true` to Babel opts to force compile as LSC - Updated test suite --- src/config.lsc | 178 ++++++++++-------- src/index.lsc | 62 ++---- src/state/compilerState.lsc | 61 +++--- test/fixtures/bang-calls/options.json | 1 + test/fixtures/comprehensions/options.json | 2 +- .../existential-expression/options.json | 2 +- .../for-in/assign-in-without-now/expected.js | 1 + test/fixtures/linter/options.json | 2 +- test/fixtures/match/options.json | 2 +- test/fixtures/options.json | 2 +- test/fixtures/placeholder-args/options.json | 2 +- .../outside-function-illegal/expected.js | 1 + .../safe-call-expression/options.json | 2 +- .../lightscript-stdlib-works/options.json | 2 +- .../no-now-update-assignment/expected.js | 1 + .../variables/shadow-basic/expected.js | 4 + .../variables/shadow-nested/expected.js | 7 + .../shadow-same-level-dup/expected.js | 4 + .../variables/shadow-toplevel-dup/expected.js | 2 + .../expected.js | 1 + 20 files changed, 168 insertions(+), 171 deletions(-) create mode 100644 test/fixtures/for-in/assign-in-without-now/expected.js create mode 100644 test/fixtures/placeholder-args/outside-function-illegal/expected.js create mode 100644 test/fixtures/variables/no-now-update-assignment/expected.js create mode 100644 test/fixtures/variables/shadow-basic/expected.js create mode 100644 test/fixtures/variables/shadow-nested/expected.js create mode 100644 test/fixtures/variables/shadow-same-level-dup/expected.js create mode 100644 test/fixtures/variables/shadow-toplevel-dup/expected.js create mode 100644 test/fixtures/whitespace/subscript-indent-enforced-illegal/expected.js diff --git a/src/config.lsc b/src/config.lsc index c773393..1436737 100644 --- a/src/config.lsc +++ b/src/config.lsc @@ -2,78 +2,91 @@ import * as babylonLightScript from "@oigroup/babylon-lightscript"; import packageMetadata from "../package.json"; import parseConfigurationDirectives from './util/parseConfigurationDirectives' -export getMetadata() -> - { - parser: babylonLightScript - getParserOpts - parseConfigurationDirectives - name: packageMetadata.name - version: packageMetadata.version - options: { - stdlib: { - description: "Configure the LightScript standard library" - } - patternMatching: { - description: "Test and branch using `match` keyword" - valueType: "option" - options: ["default", "enhanced"] - defaultValue: "default" - stage: "1" - } - whiteblock: { - description: "Disable `{` `}` as block delimiters." - valueType: "boolean" - } - bangCall: { - description: "Call functions with paren-free syntax using `!`" - valueType: "boolean" - defaultValue: true - } - safeCall: { - description: "Call a function using `?` to check callability first" - valueType: "boolean" - defaultValue: true - } - existential: { - description: "Postfix `?` checks that an expression is not loose-equal to `null`" - valueType: "boolean" - } - enhancedComprehension: { - description: "Positional comprehensions and `case` syntax for comprehensions" - valueType: "boolean" - defaultValue: true - stage: "0" - } - noEnforcedSubscriptIndentation: { - description: "Do not enforce indentation for subscripts on subsequent lines." - valueType: "boolean" - } - useRequire: { - description: "Generate `require` rather than `import` when the compiler introduces a module." - valueType: "boolean" - } - flippedImports: { - description: "Allow imports via `import 'path': [specifier]` syntax." - valueType: "boolean" - } - disableJsx: { - description: "Don't parse JSX expressions." - valueType: "boolean" - } - disableFlow: { - description: "Don't parse Flow type annotations." - valueType: "boolean" - } - placeholderArgs: { - description: "Specify arguments for a function using placeholders in the body." - valueType: "boolean" - } - placeholder: { - description: "Specify an identifier to be transformed into PlaceholderExpression." - valueType: "string" - } +export getMetadata() -> { + parser: babylonLightScript + getParserOpts + parseConfigurationDirectives + name: packageMetadata.name + version: packageMetadata.version + options: { + stdlib: { + description: "Configure the LightScript standard library" + } + patternMatching: { + description: "Test and branch using `match` keyword" + valueType: "option" + options: ["default", "enhanced"] + defaultValue: "default" + stage: "1" + } + whiteblock: { + description: "Disable `{` `}` as block delimiters." + valueType: "boolean" + } + bangCall: { + description: "Call functions with paren-free syntax using `!`" + valueType: "boolean" + defaultValue: true + } + safeCall: { + description: "Call a function using `?` to check callability first" + valueType: "boolean" + defaultValue: true + } + existential: { + description: "Postfix `?` checks that an expression is not loose-equal to `null`" + valueType: "boolean" + } + enhancedComprehension: { + description: "Positional comprehensions and `case` syntax for comprehensions" + valueType: "boolean" + defaultValue: true + stage: "0" + } + noEnforcedSubscriptIndentation: { + description: "Do not enforce indentation for subscripts on subsequent lines." + valueType: "boolean" + } + useRequire: { + description: "Generate `require` rather than `import` when the compiler introduces a module." + valueType: "boolean" + } + flippedImports: { + description: "Allow imports via `import 'path': [specifier]` syntax." + valueType: "boolean" + } + disableJsx: { + description: "Don't parse JSX expressions." + valueType: "boolean" + } + disableFlow: { + description: "Don't parse Flow type annotations." + valueType: "boolean" + } + placeholderArgs: { + description: "Specify arguments for a function using placeholders in the body." + valueType: "boolean" + } + placeholder: { + description: "Specify an identifier to be transformed into PlaceholderExpression." + valueType: "string" } } +} + +getFileTypeInfo(file) -> + if not file?.opts?.filename: return { isLightScript: true, isLSX: false } + filename = file.opts.filename + + if filename.includes(".lsx"): return { isLightScript: true, isLSX: true } + + if ( + filename === "unknown" or // eslint + filename === "repl" or // babel-node + filename.includes(".lsc") + ): return { isLightScript: true, isLSX: false } + + return { isLightScript: false, isLSX: false } export getParserOpts(pluginOpts, initialParserOpts) -> parserOpts = initialParserOpts or {} @@ -116,12 +129,29 @@ export getParserOpts(pluginOpts, initialParserOpts) -> parserOpts -export preParseInitialization(compiler, babelFile, pluginOpts) -> +// Pre-parse initialization of compiler state. All configuration data is +// collected here. +export preParseInitialization(compiler, babelFile, pluginOpts): void -> + compiler.file = babelFile + compiler.opts = pluginOpts + babelFile.parseShebang() { opts, parserOpts, code } = babelFile - directiveOpts = parseConfigurationDirectives(code) + fileTypeInfo = getFileTypeInfo(babelFile) + if fileTypeInfo.isLightScript and (pluginOpts.isLightScript == undefined): + pluginOpts.isLightScript = true + directiveOpts = parseConfigurationDirectives(code) + Object.assign(pluginOpts, directiveOpts) + if pluginOpts.isLightScript: + compiler.isLightScript = true + babelFile.isLightScript = true + compiler.isLSX = pluginOpts.isLSX + babelFile.isLSX = pluginOpts.isLSX -// XXX: shouldn't be needed, refactor -export { parseConfigurationDirectives } + // Set up LSC parsing pipeline + opts.parserOpts = opts.parserOpts or {} + nextParserOpts = getParserOpts(pluginOpts, opts.parserOpts) + parserOpts.parse = nextParserOpts.parse + parserOpts.plugins = nextParserOpts.plugins diff --git a/src/index.lsc b/src/index.lsc index 5160aeb..b4ad787 100644 --- a/src/index.lsc +++ b/src/index.lsc @@ -1,43 +1,13 @@ -import patch from './util/babelTypesPatch' -import { registerLightscriptNodeTypes } from './lscNodeTypes' - -import { resetHelpers } from "./state/helpers"; // XXX: should be part of state init -import { getParserOpts, parseConfigurationDirectives } from './config' +import { preParseInitialization } from './config' import findBabelOptionsForPlugin from './util/findBabelOptionsForPlugin' -import { getFileTypeInfo, createCompilerState, initializeCompilerState, postprocess } from './state/compilerState' +import { createCompilerState, preCompileInitialization, postprocess } from './state/compilerState' import visitAstFixup from './visitors/fixAst' import visitPlaceholders from './visitors/placeholders' import visitOptionalChains from './visitors/optionalChains' import visitMain from './visitors/main' -// TODO: factor out to config.lsc -computeParserOpts(file, pluginOpts) -> - // By this time, we have access to the code of the file. - // We can parse shebang, directives, etc and manipulate parser options - // based on those data. - file.parseShebang() - { opts, parserOpts } = file - fileTypeInfo = getFileTypeInfo(file) - cdOpts = parseConfigurationDirectives(file.code) - - // Don't read as LSC - if (not fileTypeInfo.isLightScript) and (not cdOpts.isLightScript): return - - file.isLightScript = true - Object.assign(pluginOpts, cdOpts) - - // Get LSC parser opts - opts.parserOpts = opts.parserOpts or {} - nextParserOpts = getParserOpts(pluginOpts, opts.parserOpts) - parserOpts.parse = nextParserOpts.parse - parserOpts.plugins = nextParserOpts.plugins - export default LightScript(babel) -> - { types: t } = babel - patch(t) - registerLightscriptNodeTypes(t) - plugin = { manipulateOptions(opts, parserOpts, file) -> // manipulateOptions is called very early in the Babel processing chain. @@ -60,32 +30,22 @@ export default LightScript(babel) -> origAddCode.call(this, code) pluginOpts = findBabelOptionsForPlugin(this.opts, plugin) - // compiler = createCompilerState() - // this.lscCompiler = compiler - // compiler~preParseInitialization(this, code, this.opts, pluginOpts) - computeParserOpts(this, pluginOpts) + compiler = createCompilerState(babel) + this.lscCompiler = compiler + compiler~preParseInitialization(this, pluginOpts) return visitor: { // The compiler's main loop is a Program visitor. Program(path, state): void -> - ////////// Initialize compilation run - // Obtain configuration - opts = state.opts - compiler = createCompilerState({ - programPath: path - babel - opts - file: state.file - }) + // Skip non-LightScript code + if not state.file.isLightScript: return - // Early-out if not a LightScript program - if (!compiler.isLightScript) return + // Obtain compiler state created during early processing + compiler = state.file.lscCompiler - initializeCompilerState() - - // TODO: helpers -> compilerState - resetHelpers(path) + ////////// Initialize compilation run + compiler~preCompileInitialization(path) ////////// AST visitation and transformation // First pass: Perform basic ast fixups (block bodies, etc) diff --git a/src/state/compilerState.lsc b/src/state/compilerState.lsc index cf84576..1f28996 100644 --- a/src/state/compilerState.lsc +++ b/src/state/compilerState.lsc @@ -3,6 +3,7 @@ import { registerLightscriptNodeTypes } from '../lscNodeTypes' import { initializeStdlib } from './stdlib' import { initializeRuntime } from './runtime' import { insertImports } from './imports' +import { resetHelpers } from './helpers' import { getInlinedOperatorsEnabled } from '../transforms/inlinedOperators' // XXX import { getLoc, span } from 'ast-loc-utils' @@ -32,9 +33,9 @@ let compilerState = { // lightscript-runtime state runtime: null - inlinedOperatorsEnabled: true - isLightScript: true - isLSX: false + inlinedOperatorsEnabled: null + isLightScript: null + isLSX: null } export getCompilerState() -> compilerState @@ -43,24 +44,6 @@ export getBabel() -> compilerState.babel export getOptions() -> compilerState.opts -export getFileTypeInfo(file) -> - if not file?.opts?.filename: return { isLightScript: true, isLSX: false } - filename = file.opts.filename - - if filename.includes(".lsx"): return { isLightScript: true, isLSX: true } - - if ( - filename === "unknown" or // eslint - filename === "repl" or // babel-node - filename.includes(".lsc") or - filename.includes("test/fixtures") - ): return { isLightScript: true, isLSX: false } - - return { isLightScript: false, isLSX: false } - -processFilename(state): void -> - Object.assign(state, getFileTypeInfo(state.file)) - // This will get the position of the first non-comment token. We place imports // and requires here because ESLint requires the corresponding string literals // to be on top of a valid token, or it chokes. @@ -72,37 +55,39 @@ findFirstTokenLoc(tokens) -> return null -export createCompilerState(initialState) -> - now compilerState = initialState - processFilename(compilerState) - // Allow config directives to override filename processing - if initialState.file?.isLightScript: compilerState.isLightScript = true +export createCompilerState(babel) -> + // TODO: Eliminate global here. Compiler should pass state down using + // `~` where necessary. + now compilerState = { babel } + compilerState + +export preCompileInitialization(compiler, programPath) -> + { babel, opts, file } = compiler + compiler.programPath = programPath + + patch(babel.types) + registerLightscriptNodeTypes(babel.types) // Get zero-location - tokens = initialState?.file?.ast?.tokens - body = initialState?.programPath?.node?.body + tokens = file?.ast?.tokens + body = programPath?.node?.body loc = findFirstTokenLoc(tokens) if loc: - initialState.firstLoc = loc + compiler.firstLoc = loc elif body and body.length > 0: - initialState.firstLoc = getLoc(body[0])~span(1) + compiler.firstLoc = getLoc(body[0])~span(1) else: - initialState.firstLoc = { + compiler.firstLoc = { start: 0 end: 1 loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } } } - return compilerState - -export initializeCompilerState() -> - { babel, opts } = compilerState - patch(babel.types) - registerLightscriptNodeTypes(babel.types) - + // XXX: fix this compilerState.stdlib = initializeStdlib(opts) compilerState.inlinedOperatorsEnabled = getInlinedOperatorsEnabled(opts) compilerState.runtime = initializeRuntime(opts) + resetHelpers(programPath) compilerState diff --git a/test/fixtures/bang-calls/options.json b/test/fixtures/bang-calls/options.json index 54dd895..dc52ee5 100644 --- a/test/fixtures/bang-calls/options.json +++ b/test/fixtures/bang-calls/options.json @@ -1,6 +1,7 @@ { "plugins": [ ["lightscript", { + "isLightScript": true, "bangCall": true, "safeCall": true }] diff --git a/test/fixtures/comprehensions/options.json b/test/fixtures/comprehensions/options.json index d3cbf9b..9f8ecda 100644 --- a/test/fixtures/comprehensions/options.json +++ b/test/fixtures/comprehensions/options.json @@ -1,3 +1,3 @@ { - "plugins": [["lightscript", { "enhancedComprehension": false }]] + "plugins": [["lightscript", { "isLightScript": true, "enhancedComprehension": false }]] } diff --git a/test/fixtures/existential-expression/options.json b/test/fixtures/existential-expression/options.json index 583fae7..59256ab 100644 --- a/test/fixtures/existential-expression/options.json +++ b/test/fixtures/existential-expression/options.json @@ -1,3 +1,3 @@ { - "plugins": [["lightscript", { "existential": true }]] + "plugins": [["lightscript", { "isLightScript": true, "existential": true }]] } diff --git a/test/fixtures/for-in/assign-in-without-now/expected.js b/test/fixtures/for-in/assign-in-without-now/expected.js new file mode 100644 index 0000000..e4a96d2 --- /dev/null +++ b/test/fixtures/for-in/assign-in-without-now/expected.js @@ -0,0 +1 @@ +for (const k in obj = complexFunction()) {} \ No newline at end of file diff --git a/test/fixtures/linter/options.json b/test/fixtures/linter/options.json index 1d409fa..71c9f89 100644 --- a/test/fixtures/linter/options.json +++ b/test/fixtures/linter/options.json @@ -1,3 +1,3 @@ { - "plugins": [["lightscript", {"__linter": true}]] + "plugins": [["lightscript", {"isLightScript": true, "__linter": true}]] } diff --git a/test/fixtures/match/options.json b/test/fixtures/match/options.json index b7500eb..2530ea7 100644 --- a/test/fixtures/match/options.json +++ b/test/fixtures/match/options.json @@ -1,3 +1,3 @@ { - "plugins": [["lightscript", { "useRequire": true }]] + "plugins": [["lightscript", { "isLightScript": true, "useRequire": true }]] } diff --git a/test/fixtures/options.json b/test/fixtures/options.json index 47bd8cd..57c8952 100644 --- a/test/fixtures/options.json +++ b/test/fixtures/options.json @@ -1,3 +1,3 @@ { - "plugins": ["lightscript"] + "plugins": [["lightscript", { "isLightScript": true }]] } diff --git a/test/fixtures/placeholder-args/options.json b/test/fixtures/placeholder-args/options.json index 0e3c005..8c8794c 100644 --- a/test/fixtures/placeholder-args/options.json +++ b/test/fixtures/placeholder-args/options.json @@ -1,3 +1,3 @@ { - "plugins": [["lightscript", { "placeholderArgs": true }]] + "plugins": [["lightscript", { "isLightScript": true, "placeholderArgs": true }]] } diff --git a/test/fixtures/placeholder-args/outside-function-illegal/expected.js b/test/fixtures/placeholder-args/outside-function-illegal/expected.js new file mode 100644 index 0000000..f8c2d64 --- /dev/null +++ b/test/fixtures/placeholder-args/outside-function-illegal/expected.js @@ -0,0 +1 @@ +_ + 1; \ No newline at end of file diff --git a/test/fixtures/safe-call-expression/options.json b/test/fixtures/safe-call-expression/options.json index bbb15b6..f0472f4 100644 --- a/test/fixtures/safe-call-expression/options.json +++ b/test/fixtures/safe-call-expression/options.json @@ -1,3 +1,3 @@ { - "plugins": [["lightscript", { "safeCall": true }]] + "plugins": [["lightscript", { "isLightScript": true, "safeCall": true }]] } diff --git a/test/fixtures/stdlib/lightscript-stdlib-works/options.json b/test/fixtures/stdlib/lightscript-stdlib-works/options.json index 77ff339..72828dc 100644 --- a/test/fixtures/stdlib/lightscript-stdlib-works/options.json +++ b/test/fixtures/stdlib/lightscript-stdlib-works/options.json @@ -1,3 +1,3 @@ { - "plugins": ["lightscript", "transform-es2015-modules-commonjs"] + "plugins": [["lightscript", {"isLightScript": true}], "transform-es2015-modules-commonjs"] } diff --git a/test/fixtures/variables/no-now-update-assignment/expected.js b/test/fixtures/variables/no-now-update-assignment/expected.js new file mode 100644 index 0000000..b270450 --- /dev/null +++ b/test/fixtures/variables/no-now-update-assignment/expected.js @@ -0,0 +1 @@ +x += 2; \ No newline at end of file diff --git a/test/fixtures/variables/shadow-basic/expected.js b/test/fixtures/variables/shadow-basic/expected.js new file mode 100644 index 0000000..bf07460 --- /dev/null +++ b/test/fixtures/variables/shadow-basic/expected.js @@ -0,0 +1,4 @@ +let x; +if (true) { + x = 1; +} \ No newline at end of file diff --git a/test/fixtures/variables/shadow-nested/expected.js b/test/fixtures/variables/shadow-nested/expected.js new file mode 100644 index 0000000..87b3d94 --- /dev/null +++ b/test/fixtures/variables/shadow-nested/expected.js @@ -0,0 +1,7 @@ +if (true) { + function x() {} +} + +if (true) { + x = 1; +} \ No newline at end of file diff --git a/test/fixtures/variables/shadow-same-level-dup/expected.js b/test/fixtures/variables/shadow-same-level-dup/expected.js new file mode 100644 index 0000000..731be46 --- /dev/null +++ b/test/fixtures/variables/shadow-same-level-dup/expected.js @@ -0,0 +1,4 @@ +if (true) { + let x; + x = 1; +} \ No newline at end of file diff --git a/test/fixtures/variables/shadow-toplevel-dup/expected.js b/test/fixtures/variables/shadow-toplevel-dup/expected.js new file mode 100644 index 0000000..72b1b61 --- /dev/null +++ b/test/fixtures/variables/shadow-toplevel-dup/expected.js @@ -0,0 +1,2 @@ +let x; +x = 1; \ No newline at end of file diff --git a/test/fixtures/whitespace/subscript-indent-enforced-illegal/expected.js b/test/fixtures/whitespace/subscript-indent-enforced-illegal/expected.js new file mode 100644 index 0000000..7d32d09 --- /dev/null +++ b/test/fixtures/whitespace/subscript-indent-enforced-illegal/expected.js @@ -0,0 +1 @@ +a.b; \ No newline at end of file