Repository: buxlabs/boxwood (js)

Issue:
Pages with globals don't generate properly
Here's the input 

```html
<import layout from='layouts/base.html' />

<layout>
  <h1 class="{website.dummy}">{ website.dummy  || 'hello'}</h1>
</layout>
```

```html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <h1>Layout {globals.website.dummy}</h1>

  <slot />
</body>

</html>
```

Here's the compilation file:

```js
const fs = require('fs-extra');
const { compile, escape } = require('pure-engine')

async function example() {
  const content = await fs.readFile('website/pages/about.html', 'utf-8')
  const { template, warnings, errors } = await compile(content, { cache: false, paths: ['website/pages', 'website/components'] })
  console.log(template({ website: { dummy: 'baz' } }, escape))
	console.log(warnings);
	console.log(errors);

	
}

example()
```

The layout is not generated and I'm getting the following error:

```
Cannot read property 'reduce' of undefined
at complexReplace (/Users/zaiste/Workspace/pure-playground/node_modules/abstract-syntax-tree/src/replace.js:12:31)
```

Pull request:
Better globals support
Closes #592 

Patch:
diff --git a/src/Scope.js b/src/Scope.js
index 5b98598..c2b8276 100644
--- a/src/Scope.js
+++ b/src/Scope.js
@@ -1,20 +1,6 @@
 const { replace } = require('abstract-syntax-tree')
-const { GLOBAL_VARIABLE, OBJECT_VARIABLE } = require('./utilities/enum')
-
-function isMemberExpression (node) {
-  return node.type === 'MemberExpression'
-}
-
-function isIdentifier (node, name) {
-  return node.type === 'Identifier' && node.name === name
-}
-
-function isGlobalVariable (node) {
-  return isMemberExpression(node) &&
-    isMemberExpression(node.object) &&
-    isIdentifier(node.object.object, OBJECT_VARIABLE) &&
-    isIdentifier(node.object.property, GLOBAL_VARIABLE)
-}
+const { OBJECT_VARIABLE } = require('./utilities/enum')
+const { isGlobalVariable } = require('./utilities/globals')
 
 class Scope {
   flatten (tree) {
diff --git a/src/utilities/globals.js b/src/utilities/globals.js
new file mode 100644
index 0000000..f921315
--- /dev/null
+++ b/src/utilities/globals.js
@@ -0,0 +1,43 @@
+const { GLOBAL_VARIABLE, OBJECT_VARIABLE } = require('./enum')
+
+function isMemberExpression (node) {
+  return node.type === 'MemberExpression'
+}
+
+function isIdentifier (node, name) {
+  return node.type === 'Identifier' && node.name === name
+}
+
+function isMemberExpressionWithGlobalVariable (node) {
+  return isMemberExpression(node) &&
+    isIdentifier(node.object, OBJECT_VARIABLE) &&
+    isIdentifier(node.property, GLOBAL_VARIABLE)
+}
+
+function isComplexMemberExpressionWithGlobalVariable (node) {
+  return isMemberExpression(node) && isMemberExpressionWithGlobalVariable(node.object)
+}
+
+function isExpressionStatementWithGlobalVariable (node) {
+  return node.type === 'ExpressionStatement' && isComplexMemberExpressionWithGlobalVariable(node.expression)
+}
+
+function isComplexGlobalVariable (node) {
+  if (node.type !== 'ExpressionStatement') { return false }
+  node = node.expression
+  if (node.type !== 'MemberExpression') { return false }
+  while (node.object.type === 'MemberExpression') {
+    node = node.object
+  }
+  return isIdentifier(node.object, GLOBAL_VARIABLE)
+}
+
+function isGlobalVariable (node) {
+  if (!node) { return false }
+  return isMemberExpressionWithGlobalVariable(node) ||
+    isComplexMemberExpressionWithGlobalVariable(node) ||
+    isExpressionStatementWithGlobalVariable(node) ||
+    isComplexGlobalVariable(node)
+}
+
+module.exports = { isGlobalVariable }
diff --git a/src/utilities/optimize.js b/src/utilities/optimize.js
index 6a249d4..621fcd0 100644
--- a/src/utilities/optimize.js
+++ b/src/utilities/optimize.js
@@ -14,8 +14,8 @@ const { parse, stringify } = require('./html')
 const { inlineLocalVariablesInTags } = require('./inline')
 const walk = require('himalaya-walk')
 const { addPlaceholders, removePlaceholders } = require('./keywords')
-const { GLOBAL_VARIABLE } = require('./enum')
 const { parseData, getDataFormat } = require('./data')
+const { isGlobalVariable } = require('./globals')
 const { isPlainObject } = require('pure-conditions')
 
 function canInlineTree ({ body }) {
@@ -89,19 +89,31 @@ function falsyCodeRemoval (node) {
   return node
 }
 
-function undefinedOptionsRemoval (node) {
-  if (node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'undefined') {
-    return null
-  }
-  return node
+function isMemberExpression (node) {
+  return node.type === 'ExpressionStatement' && node.expression.type === 'MemberExpression'
 }
 
-function isGlobalVariable (tree) {
-  const node = tree.body[0]
+function isUndefinedMemberExpression (node) {
   return node.type === 'ExpressionStatement' &&
     node.expression.type === 'MemberExpression' &&
     node.expression.object.type === 'Identifier' &&
-    node.expression.object.name === GLOBAL_VARIABLE
+    node.expression.object.name === 'undefined'
+}
+
+function isComplexUndefinedMemberExpression (node) {
+  if (!isMemberExpression(node)) { return false }
+  node = node.expression
+  while (node.object.type === 'MemberExpression') {
+    node = node.object
+  }
+  return isUndefinedMemberExpression(node)
+}
+
+function undefinedOptionsRemoval (node) {
+  if (isUndefinedMemberExpression(node) || isComplexUndefinedMemberExpression(node)) {
+    return null
+  }
+  return node
 }
 
 function optimizeCurlyTag (value, variables, newVariables, loose) {
@@ -109,7 +121,9 @@ function optimizeCurlyTag (value, variables, newVariables, loose) {
   value = addPlaceholders(value)
   if (isObject(value)) value = `(${value})`
   const tree = new AbstractSyntaxTree(value)
-  if (isGlobalVariable(tree)) { return curlyTag(value) }
+  if (isGlobalVariable(tree.body[0])) {
+    return curlyTag(value)
+  }
   tree.replace({ enter: removePlaceholders })
   tree.replace({ enter: (node, parent) => inlineVariables(node, parent, variables, newVariables) })
   tree.replace({ enter: memberExpressionReduction })


Test patch:
diff --git a/test/fixtures/acceptance/globals/actual.html b/test/fixtures/acceptance/globals/actual.html
index d88c8ab..0b4cf21 100644
--- a/test/fixtures/acceptance/globals/actual.html
+++ b/test/fixtures/acceptance/globals/actual.html
@@ -1,2 +1,5 @@
 <import layout from="layouts/default.html"/>
-<layout>{globals.bar}</layout>
+<layout>
+  {globals.bar}
+  {globals.qux.quux}
+</layout>
diff --git a/test/fixtures/acceptance/globals/data.json b/test/fixtures/acceptance/globals/data.json
index 15a6f26..3a30fee 100644
--- a/test/fixtures/acceptance/globals/data.json
+++ b/test/fixtures/acceptance/globals/data.json
@@ -1,5 +1,14 @@
 {
   "foo": "foo",
   "bar": "bar",
-  "baz": "baz"
+  "baz": "baz",
+  "qux": {
+    "quux": "quuux",
+    "ban": {
+      "bat": "bat",
+      "pl": "PL",
+      "en": "EN"
+    }
+  },
+  "language": "en"
 }
diff --git a/test/fixtures/acceptance/globals/expected.html b/test/fixtures/acceptance/globals/expected.html
index 86e041d..5b884c6 100644
--- a/test/fixtures/acceptance/globals/expected.html
+++ b/test/fixtures/acceptance/globals/expected.html
@@ -1,3 +1,7 @@
 foo
 bar
+quuux
 baz
+bat
+en
+EN
diff --git a/test/fixtures/acceptance/globals/layouts/default.html b/test/fixtures/acceptance/globals/layouts/default.html
index 50359c1..fbb746b 100644
--- a/test/fixtures/acceptance/globals/layouts/default.html
+++ b/test/fixtures/acceptance/globals/layouts/default.html
@@ -3,3 +3,6 @@
 <header/>
 <slot/>
 {globals.baz}
+{globals.qux.ban.bat}
+{globals.language}
+{globals.qux.ban[globals.language]}
diff --git a/test/spec/acceptance.spec.js b/test/spec/acceptance.spec.js
index a5b892e..d5e1386 100644
--- a/test/spec/acceptance.spec.js
+++ b/test/spec/acceptance.spec.js
@@ -143,13 +143,13 @@ test('acceptance: globals', async assert => {
   assert.deepEqual(actual, expected)
 })
 
-test('acceptance: static-page', async assert => {
-  const { actual, expected } = await suite('static-page')
+test('acceptance: globals-env', async assert => {
+  const { actual, expected } = await suite('globals-env')
   assert.deepEqual(actual, expected)
 })
 
-test('acceptance: globals-env', async assert => {
-  const { actual, expected } = await suite('globals-env')
+test('acceptance: static-page', async assert => {
+  const { actual, expected } = await suite('static-page')
   assert.deepEqual(actual, expected)
 })