From 79d8c5aa144f70e5326bb1bc173d8b4b359812e3 Mon Sep 17 00:00:00 2001 From: James Talmage Date: Sat, 2 Jul 2016 03:04:49 -0400 Subject: [PATCH 1/4] find list of applicable configs without requiring babel-core or plugins `build-config-chain.js` creates an array of options which will be passed to `OptionsManager#mergeOptions`. The advantage of separating it out is that `build-config-chain` has a very minimal dependency list. The eventual intent is to allow the require hook to lazy load only when required. In other words, if no required files ever match the patterns `ignore` / `only` patterns, the bulk of babel-core, and the associated plugins, will never be loaded. --- .../file/options/build-config-chain.js | 178 ++++++++++++++++++ .../file/options/option-manager.js | 138 +------------- 2 files changed, 181 insertions(+), 135 deletions(-) create mode 100644 packages/babel-core/src/transformation/file/options/build-config-chain.js diff --git a/packages/babel-core/src/transformation/file/options/build-config-chain.js b/packages/babel-core/src/transformation/file/options/build-config-chain.js new file mode 100644 index 0000000000..0c847f8672 --- /dev/null +++ b/packages/babel-core/src/transformation/file/options/build-config-chain.js @@ -0,0 +1,178 @@ + +import type Logger from "../logger"; +import resolve from "../../../helpers/resolve"; +import json5 from "json5"; +import isAbsolute from "path-is-absolute"; +import pathExists from "path-exists"; +import path from "path"; +import fs from "fs"; + +let existsCache = {}; +let jsonCache = {}; + +const BABELIGNORE_FILENAME = ".babelignore"; +const BABELRC_FILENAME = ".babelrc"; +const PACKAGE_FILENAME = "package.json"; + +function exists(filename) { + let cached = existsCache[filename]; + if (cached == null) { + return existsCache[filename] = pathExists.sync(filename); + } else { + return cached; + } +} + +export default function buildConfigChain(opts: Object = {}, log?: Logger) { + let filename = opts.filename; + let builder = new ConfigChainBuilder(log); + + // resolve all .babelrc files + if (opts.babelrc !== false) { + builder.findConfigs(filename); + } + + builder.mergeConfig({ + options: opts, + alias: "base", + dirname: filename && path.dirname(filename) + }); + + return builder.configs; +} + +class ConfigChainBuilder { + constructor(log?: Logger) { + this.resolvedConfigs = []; + this.configs = []; + this.log = log; + } + + findConfigs(loc) { + if (!loc) return; + + if (!isAbsolute(loc)) { + loc = path.join(process.cwd(), loc); + } + + let foundConfig = false; + let foundIgnore = false; + + while (loc !== (loc = path.dirname(loc))) { + if (!foundConfig) { + let configLoc = path.join(loc, BABELRC_FILENAME); + if (exists(configLoc)) { + this.addConfig(configLoc); + foundConfig = true; + } + + let pkgLoc = path.join(loc, PACKAGE_FILENAME); + if (!foundConfig && exists(pkgLoc)) { + foundConfig = this.addConfig(pkgLoc, "babel", JSON); + } + } + + if (!foundIgnore) { + let ignoreLoc = path.join(loc, BABELIGNORE_FILENAME); + if (exists(ignoreLoc)) { + this.addIgnoreConfig(ignoreLoc); + foundIgnore = true; + } + } + + if (foundIgnore && foundConfig) return; + } + } + + addIgnoreConfig(loc) { + let file = fs.readFileSync(loc, "utf8"); + let lines = file.split("\n"); + + lines = lines + .map((line) => line.replace(/#(.*?)$/, "").trim()) + .filter((line) => !!line); + + if (lines.length) { + this.mergeConfig({ + options: { ignore: lines }, + loc + }); + } + } + + addConfig(loc: string, key?: string, json = json5): boolean { + if (this.resolvedConfigs.indexOf(loc) >= 0) { + return false; + } + + this.resolvedConfigs.push(loc); + + let content = fs.readFileSync(loc, "utf8"); + let options; + + try { + options = jsonCache[content] = jsonCache[content] || json.parse(content); + if (key) options = options[key]; + } catch (err) { + err.message = `${loc}: Error while parsing JSON - ${err.message}`; + throw err; + } + + this.mergeConfig({ + options, + alias: loc, + dirname: path.dirname(loc) + }); + + return !!options; + } + + mergeConfig({ + options, + alias, + loc, + dirname + }) { + if (!options) { + return false; + } + + options = Object.assign({}, options); + + dirname = dirname || process.cwd(); + loc = loc || alias; + + // add extends clause + if (options.extends) { + let extendsLoc = resolve(options.extends, dirname); + if (extendsLoc) { + this.addConfig(extendsLoc); + } else { + if (this.log) this.log.error(`Couldn't resolve extends clause of ${options.extends} in ${alias}`); + } + delete options.extends; + } + + this.configs.push({ + options, + alias, + loc, + dirname + }); + + // env + let envOpts; + let envKey = process.env.BABEL_ENV || process.env.NODE_ENV || "development"; + if (options.env) { + envOpts = options.env[envKey]; + delete options.env; + } + + this.mergeConfig({ + options: envOpts, + alias: `${alias}.env.${envKey}`, + dirname: dirname + }); + } +} + diff --git a/packages/babel-core/src/transformation/file/options/option-manager.js b/packages/babel-core/src/transformation/file/options/option-manager.js index ee59b46a70..dcdcacc024 100644 --- a/packages/babel-core/src/transformation/file/options/option-manager.js +++ b/packages/babel-core/src/transformation/file/options/option-manager.js @@ -6,32 +6,13 @@ import Plugin from "../../plugin"; import * as messages from "babel-messages"; import { normaliseOptions } from "./index"; import resolve from "../../../helpers/resolve"; -import json5 from "json5"; -import isAbsolute from "path-is-absolute"; -import pathExists from "path-exists"; import cloneDeepWith from "lodash/cloneDeepWith"; import clone from "lodash/clone"; import merge from "../../../helpers/merge"; import config from "./config"; import removed from "./removed"; +import buildConfigChain from "./build-config-chain"; import path from "path"; -import fs from "fs"; - -let existsCache = {}; -let jsonCache = {}; - -const BABELIGNORE_FILENAME = ".babelignore"; -const BABELRC_FILENAME = ".babelrc"; -const PACKAGE_FILENAME = "package.json"; - -function exists(filename) { - let cached = existsCache[filename]; - if (cached == null) { - return existsCache[filename] = pathExists.sync(filename); - } else { - return cached; - } -} type PluginObject = { pre?: Function; @@ -156,32 +137,6 @@ export default class OptionManager { }); } - addConfig(loc: string, key?: string, json = json5): boolean { - if (this.resolvedConfigs.indexOf(loc) >= 0) { - return false; - } - - let content = fs.readFileSync(loc, "utf8"); - let opts; - - try { - opts = jsonCache[content] = jsonCache[content] || json.parse(content); - if (key) opts = opts[key]; - } catch (err) { - err.message = `${loc}: Error while parsing JSON - ${err.message}`; - throw err; - } - - this.mergeOptions({ - options: opts, - alias: loc, - dirname: path.dirname(loc) - }); - this.resolvedConfigs.push(loc); - - return !!opts; - } - /** * This is called when we want to merge the input `opts` into the * base options (passed as the `extendingOpts`: at top-level it's the @@ -241,17 +196,6 @@ export default class OptionManager { opts.plugins = OptionManager.normalisePlugins(loc, dirname, opts.plugins); } - // add extends clause - if (opts.extends) { - let extendsLoc = resolve(opts.extends, dirname); - if (extendsLoc) { - this.addConfig(extendsLoc); - } else { - if (this.log) this.log.error(`Couldn't resolve extends clause of ${opts.extends} in ${alias}`); - } - delete opts.extends; - } - // resolve presets if (opts.presets) { // If we're in the "pass per preset" mode, we resolve the presets @@ -273,14 +217,6 @@ export default class OptionManager { } } - // env - let envOpts; - let envKey = process.env.BABEL_ENV || process.env.NODE_ENV || "development"; - if (opts.env) { - envOpts = opts.env[envKey]; - delete opts.env; - } - // Merge them into current extending options in case of top-level // options. In case of presets, just re-assign options which are got // normalized during the `mergeOptions`. @@ -289,14 +225,6 @@ export default class OptionManager { } else { merge(extendingOpts || this.options, opts); } - - // merge in env options - this.mergeOptions({ - options: envOpts, - extending: extendingOpts, - alias: `${alias}.env.${envKey}`, - dirname: dirname - }); } /** @@ -338,56 +266,6 @@ export default class OptionManager { }); } - addIgnoreConfig(loc) { - let file = fs.readFileSync(loc, "utf8"); - let lines = file.split("\n"); - - lines = lines - .map((line) => line.replace(/#(.*?)$/, "").trim()) - .filter((line) => !!line); - - this.mergeOptions({ - options: { ignore: lines }, - loc - }); - } - - findConfigs(loc) { - if (!loc) return; - - if (!isAbsolute(loc)) { - loc = path.join(process.cwd(), loc); - } - - let foundConfig = false; - let foundIgnore = false; - - while (loc !== (loc = path.dirname(loc))) { - if (!foundConfig) { - let configLoc = path.join(loc, BABELRC_FILENAME); - if (exists(configLoc)) { - this.addConfig(configLoc); - foundConfig = true; - } - - let pkgLoc = path.join(loc, PACKAGE_FILENAME); - if (!foundConfig && exists(pkgLoc)) { - foundConfig = this.addConfig(pkgLoc, "babel", JSON); - } - } - - if (!foundIgnore) { - let ignoreLoc = path.join(loc, BABELIGNORE_FILENAME); - if (exists(ignoreLoc)) { - this.addIgnoreConfig(ignoreLoc); - foundIgnore = true; - } - } - - if (foundIgnore && foundConfig) return; - } - } - normaliseOptions() { let opts = this.options; @@ -408,20 +286,10 @@ export default class OptionManager { } init(opts: Object = {}): Object { - let filename = opts.filename; - - // resolve all .babelrc files - if (opts.babelrc !== false) { - this.findConfigs(filename); + for (let config of buildConfigChain(opts, this.log)) { + this.mergeOptions(config); } - // merge in base options - this.mergeOptions({ - options: opts, - alias: "base", - dirname: filename && path.dirname(filename) - }); - // normalise this.normaliseOptions(opts); From 62ad67e5d99f58248b32132986adc994d9efa80e Mon Sep 17 00:00:00 2001 From: James Talmage Date: Sun, 3 Jul 2016 23:58:44 -0400 Subject: [PATCH 2/4] add tests for build-config-chain --- .../file/options/build-config-chain.js | 3 +- packages/babel-core/test/config-chain.js | 101 ++++++++++++++++++ .../test/fixtures/config/.babelignore | 1 + .../babel-core/test/fixtures/config/.babelrc | 4 + .../test/fixtures/config/dir1/src.js | 1 + .../test/fixtures/config/dir2/.babelrc | 5 + .../test/fixtures/config/dir2/src.js | 1 + .../fixtures/config/extended.babelrc.json | 5 + 8 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 packages/babel-core/test/config-chain.js create mode 100644 packages/babel-core/test/fixtures/config/.babelignore create mode 100644 packages/babel-core/test/fixtures/config/.babelrc create mode 100644 packages/babel-core/test/fixtures/config/dir1/src.js create mode 100644 packages/babel-core/test/fixtures/config/dir2/.babelrc create mode 100644 packages/babel-core/test/fixtures/config/dir2/src.js create mode 100644 packages/babel-core/test/fixtures/config/extended.babelrc.json diff --git a/packages/babel-core/src/transformation/file/options/build-config-chain.js b/packages/babel-core/src/transformation/file/options/build-config-chain.js index 0c847f8672..f9b43e3a6b 100644 --- a/packages/babel-core/src/transformation/file/options/build-config-chain.js +++ b/packages/babel-core/src/transformation/file/options/build-config-chain.js @@ -95,7 +95,8 @@ class ConfigChainBuilder { if (lines.length) { this.mergeConfig({ options: { ignore: lines }, - loc + alias: loc, + dirname: path.dirname(loc) }); } } diff --git a/packages/babel-core/test/config-chain.js b/packages/babel-core/test/config-chain.js new file mode 100644 index 0000000000..7c85714f82 --- /dev/null +++ b/packages/babel-core/test/config-chain.js @@ -0,0 +1,101 @@ +var assert = require("assert"); +var path = require("path"); +var buildConfigChain = require("../lib/transformation/file/options/build-config-chain"); + +function fixture() { + var args = [__dirname, "fixtures", "config"]; + for (var i = 0; i < arguments.length; i ++) { + args.push(arguments[i]); + } + return path.join.apply(path, args); +} + +suite("evaluation", function () { + test("dir1", function () { + var chain = buildConfigChain({ + filename: fixture("dir1", "src.js") + }); + + var expected = [ + { + options: { + plugins: [ + "extended" + ] + }, + alias: fixture("extended.babelrc.json"), + loc: fixture("extended.babelrc.json"), + dirname: fixture() + }, + { + options: { + plugins: [ + "root" + ] + }, + alias: fixture(".babelrc"), + loc: fixture(".babelrc"), + dirname: fixture() + }, + { + options: { + ignore: [ + "root-ignore" + ] + }, + alias: fixture(".babelignore"), + loc: fixture(".babelignore"), + dirname: fixture() + }, + { + options: { + filename: fixture("dir1", "src.js") + }, + alias: "base", + loc: "base", + dirname: fixture("dir1") + } + ]; + + assert.deepEqual(chain, expected); + }); + + test("dir2", function () { + var chain = buildConfigChain({ + filename: fixture("dir2", "src.js") + }); + + var expected = [ + { + options: { + plugins: [ + "dir2" + ] + }, + alias: fixture("dir2", ".babelrc"), + loc: fixture("dir2", ".babelrc"), + dirname: fixture("dir2") + }, + { + options: { + ignore: [ + "root-ignore" + ] + }, + alias: fixture(".babelignore"), + loc: fixture(".babelignore"), + dirname: fixture() + }, + { + options: { + filename: fixture("dir2", "src.js") + }, + alias: "base", + loc: "base", + dirname: fixture("dir2") + } + ]; + + assert.deepEqual(chain, expected); + }); +}); diff --git a/packages/babel-core/test/fixtures/config/.babelignore b/packages/babel-core/test/fixtures/config/.babelignore new file mode 100644 index 0000000000..054b9bf2d3 --- /dev/null +++ b/packages/babel-core/test/fixtures/config/.babelignore @@ -0,0 +1 @@ +root-ignore diff --git a/packages/babel-core/test/fixtures/config/.babelrc b/packages/babel-core/test/fixtures/config/.babelrc new file mode 100644 index 0000000000..3dc5d1fa19 --- /dev/null +++ b/packages/babel-core/test/fixtures/config/.babelrc @@ -0,0 +1,4 @@ +{ + "plugins": ["root"], + "extends": "./extended.babelrc.json" +} diff --git a/packages/babel-core/test/fixtures/config/dir1/src.js b/packages/babel-core/test/fixtures/config/dir1/src.js new file mode 100644 index 0000000000..8b1a393741 --- /dev/null +++ b/packages/babel-core/test/fixtures/config/dir1/src.js @@ -0,0 +1 @@ +// empty diff --git a/packages/babel-core/test/fixtures/config/dir2/.babelrc b/packages/babel-core/test/fixtures/config/dir2/.babelrc new file mode 100644 index 0000000000..bb094e669b --- /dev/null +++ b/packages/babel-core/test/fixtures/config/dir2/.babelrc @@ -0,0 +1,5 @@ +{ + "plugins": [ + "dir2" + ] +} diff --git a/packages/babel-core/test/fixtures/config/dir2/src.js b/packages/babel-core/test/fixtures/config/dir2/src.js new file mode 100644 index 0000000000..8b1a393741 --- /dev/null +++ b/packages/babel-core/test/fixtures/config/dir2/src.js @@ -0,0 +1 @@ +// empty diff --git a/packages/babel-core/test/fixtures/config/extended.babelrc.json b/packages/babel-core/test/fixtures/config/extended.babelrc.json new file mode 100644 index 0000000000..6996c6d74e --- /dev/null +++ b/packages/babel-core/test/fixtures/config/extended.babelrc.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "extended" + ] +} From 8e84196eb8b836ad9d97e0af546deb82f512fc2d Mon Sep 17 00:00:00 2001 From: James Talmage Date: Thu, 7 Jul 2016 16:36:34 -0400 Subject: [PATCH 3/4] add tests for `env` options --- packages/babel-core/test/config-chain.js | 160 +++++++++++++++++- .../test/fixtures/config/env/.babelrc | 11 ++ .../test/fixtures/config/env/src.js | 1 + 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 packages/babel-core/test/fixtures/config/env/.babelrc create mode 100644 packages/babel-core/test/fixtures/config/env/src.js diff --git a/packages/babel-core/test/config-chain.js b/packages/babel-core/test/config-chain.js index 7c85714f82..9e796fc29d 100644 --- a/packages/babel-core/test/config-chain.js +++ b/packages/babel-core/test/config-chain.js @@ -10,7 +10,23 @@ function fixture() { return path.join.apply(path, args); } -suite("evaluation", function () { +suite("buildConfigChain", function () { + var oldBabelEnv; + var oldNodeEnv; + + beforeEach(function () { + oldBabelEnv = process.env.BABEL_ENV; + oldNodeEnv = process.env.NODE_ENV; + + delete process.env.BABEL_ENV; + delete process.env.NODE_ENV; + }); + + afterEach(function () { + process.env.BABEL_ENV = oldBabelEnv; + process.env.NODE_ENV = oldNodeEnv; + }); + test("dir1", function () { var chain = buildConfigChain({ filename: fixture("dir1", "src.js") @@ -98,4 +114,146 @@ suite("evaluation", function () { assert.deepEqual(chain, expected); }); + + test("env - base", function () { + var chain = buildConfigChain({ + filename: fixture("env", "src.js") + }); + + var expected = [ + { + options: { + plugins: [ + "env-base" + ] + }, + alias: fixture("env", ".babelrc"), + loc: fixture("env", ".babelrc"), + dirname: fixture("env") + }, + { + options: { + ignore: [ + "root-ignore" + ] + }, + alias: fixture(".babelignore"), + loc: fixture(".babelignore"), + dirname: fixture() + }, + { + options: { + filename: fixture("env", "src.js") + }, + alias: "base", + loc: "base", + dirname: fixture("env") + } + ]; + + assert.deepEqual(chain, expected); + }); + + test("env - foo", function () { + process.env.NODE_ENV = "foo"; + + var chain = buildConfigChain({ + filename: fixture("env", "src.js") + }); + + var expected = [ + { + options: { + plugins: [ + "env-base" + ] + }, + alias: fixture("env", ".babelrc"), + loc: fixture("env", ".babelrc"), + dirname: fixture("env") + }, + { + options: { + plugins: [ + "env-foo" + ] + }, + alias: fixture("env", ".babelrc.env.foo"), + loc: fixture("env", ".babelrc.env.foo"), + dirname: fixture("env") + }, + { + options: { + ignore: [ + "root-ignore" + ] + }, + alias: fixture(".babelignore"), + loc: fixture(".babelignore"), + dirname: fixture() + }, + { + options: { + filename: fixture("env", "src.js") + }, + alias: "base", + loc: "base", + dirname: fixture("env") + } + ]; + + assert.deepEqual(chain, expected); + }); + + test("env - bar", function () { + process.env.NODE_ENV = "foo"; // overridden + process.env.NODE_ENV = "bar"; + + var chain = buildConfigChain({ + filename: fixture("env", "src.js") + }); + + var expected = [ + { + options: { + plugins: [ + "env-base" + ] + }, + alias: fixture("env", ".babelrc"), + loc: fixture("env", ".babelrc"), + dirname: fixture("env") + }, + { + options: { + plugins: [ + "env-bar" + ] + }, + alias: fixture("env", ".babelrc.env.bar"), + loc: fixture("env", ".babelrc.env.bar"), + dirname: fixture("env") + }, + { + options: { + ignore: [ + "root-ignore" + ] + }, + alias: fixture(".babelignore"), + loc: fixture(".babelignore"), + dirname: fixture() + }, + { + options: { + filename: fixture("env", "src.js") + }, + alias: "base", + loc: "base", + dirname: fixture("env") + } + ]; + + assert.deepEqual(chain, expected); + }); }); diff --git a/packages/babel-core/test/fixtures/config/env/.babelrc b/packages/babel-core/test/fixtures/config/env/.babelrc new file mode 100644 index 0000000000..8804d3b2fb --- /dev/null +++ b/packages/babel-core/test/fixtures/config/env/.babelrc @@ -0,0 +1,11 @@ +{ + "plugins": ["env-base"], + "env": { + "foo": { + "plugins": ["env-foo"] + }, + "bar": { + "plugins": ["env-bar"] + } + } +} diff --git a/packages/babel-core/test/fixtures/config/env/src.js b/packages/babel-core/test/fixtures/config/env/src.js new file mode 100644 index 0000000000..8b1a393741 --- /dev/null +++ b/packages/babel-core/test/fixtures/config/env/src.js @@ -0,0 +1 @@ +// empty From 0a9cbe6e83bf6f2bf4100e0b14c2a4faad1abb21 Mon Sep 17 00:00:00 2001 From: James Talmage Date: Thu, 7 Jul 2016 16:58:22 -0400 Subject: [PATCH 4/4] git commit add test for package.json config --- packages/babel-core/test/config-chain.js | 44 +++++++++++++++++-- .../test/fixtures/config/pkg/.babelignore | 1 + .../test/fixtures/config/pkg/package.json | 7 +++ .../test/fixtures/config/pkg/src.js | 1 + 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 packages/babel-core/test/fixtures/config/pkg/.babelignore create mode 100644 packages/babel-core/test/fixtures/config/pkg/package.json create mode 100644 packages/babel-core/test/fixtures/config/pkg/src.js diff --git a/packages/babel-core/test/config-chain.js b/packages/babel-core/test/config-chain.js index 9e796fc29d..dbcf48b4e5 100644 --- a/packages/babel-core/test/config-chain.js +++ b/packages/babel-core/test/config-chain.js @@ -156,7 +156,7 @@ suite("buildConfigChain", function () { test("env - foo", function () { process.env.NODE_ENV = "foo"; - + var chain = buildConfigChain({ filename: fixture("env", "src.js") }); @@ -204,11 +204,11 @@ suite("buildConfigChain", function () { assert.deepEqual(chain, expected); }); - + test("env - bar", function () { process.env.NODE_ENV = "foo"; // overridden process.env.NODE_ENV = "bar"; - + var chain = buildConfigChain({ filename: fixture("env", "src.js") }); @@ -256,4 +256,42 @@ suite("buildConfigChain", function () { assert.deepEqual(chain, expected); }); + + + test("env - foo", function () { + process.env.NODE_ENV = "foo"; + + var chain = buildConfigChain({ + filename: fixture("pkg", "src.js") + }); + + var expected = [ + { + options: { + plugins: ["pkg-plugin"] + }, + alias: fixture("pkg", "package.json"), + loc: fixture("pkg", "package.json"), + dirname: fixture("pkg") + }, + { + options: { + ignore: ["pkg-ignore"] + }, + alias: fixture("pkg", ".babelignore"), + loc: fixture("pkg", ".babelignore"), + dirname: fixture("pkg") + }, + { + options: { + filename: fixture("pkg", "src.js") + }, + alias: "base", + loc: "base", + dirname: fixture("pkg") + } + ]; + + assert.deepEqual(chain, expected); + }); }); diff --git a/packages/babel-core/test/fixtures/config/pkg/.babelignore b/packages/babel-core/test/fixtures/config/pkg/.babelignore new file mode 100644 index 0000000000..902ca293de --- /dev/null +++ b/packages/babel-core/test/fixtures/config/pkg/.babelignore @@ -0,0 +1 @@ +pkg-ignore diff --git a/packages/babel-core/test/fixtures/config/pkg/package.json b/packages/babel-core/test/fixtures/config/pkg/package.json new file mode 100644 index 0000000000..94fdf25db6 --- /dev/null +++ b/packages/babel-core/test/fixtures/config/pkg/package.json @@ -0,0 +1,7 @@ +{ + "name": "application-name", + "version": "0.0.1", + "babel": { + "plugins": ["pkg-plugin"] + } +} diff --git a/packages/babel-core/test/fixtures/config/pkg/src.js b/packages/babel-core/test/fixtures/config/pkg/src.js new file mode 100644 index 0000000000..8b1a393741 --- /dev/null +++ b/packages/babel-core/test/fixtures/config/pkg/src.js @@ -0,0 +1 @@ +// empty