diff --git a/.gitignore b/.gitignore index af85823eb..b74bd67c0 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ coverage /dist/ /test-dist/ /test-generated/ +.vs/ \ No newline at end of file diff --git a/package.json b/package.json index bf11a52d1..f58c21a4f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "precommit": "lint-staged", "test": "npm run lint -s && npm run mocha -s", "test-ci": "npm run check-format && npm run test-generate-coverage", - "mocha": "mocha --timeout 10000 --max-old-space-size=768 ./test/*/*.test.js || exit 1", + "mocha": "mocha --timeout 10000 --max-old-space-size=768 ./test/*/*.test.js", "test-coverage": "npm run test-generate-coverage && nyc report --reporter=html && opn ./coverage/index.html", "test-generate-coverage": "nyc -asc npm test", "lint": "eslint .", diff --git a/src/components/taglib/TransformHelper/assignComponentId.js b/src/components/taglib/TransformHelper/assignComponentId.js index 7a61de874..68885a39f 100644 --- a/src/components/taglib/TransformHelper/assignComponentId.js +++ b/src/components/taglib/TransformHelper/assignComponentId.js @@ -106,6 +106,7 @@ module.exports = function assignComponentId(isRepeated) { } } else { // Case 3 - We need to add a unique auto key + let parentForKey = getParentForKeyVar(el, this); let uniqueKey = this.nextUniqueId(); nestedIdExpression = isRepeated @@ -114,6 +115,19 @@ module.exports = function assignComponentId(isRepeated) { idExpression = builder.literal(uniqueKey.toString()); + if (parentForKey) { + idExpression = builder.binaryExpression( + idExpression, + "+", + parentForKey + ); + nestedIdExpression = builder.binaryExpression( + nestedIdExpression, + "+", + parentForKey + ); + } + if (isCustomTag) { this.getComponentArgs().setKey(nestedIdExpression); } else { @@ -168,3 +182,66 @@ module.exports = function assignComponentId(isRepeated) { return this.componentIdInfo; }; + +const getParentForKeyVar = (el, transformHelper) => { + const context = transformHelper.context; + const builder = context.builder; + const parentFor = getParentFor(el); + + if (!parentFor) return null; + if (parentFor.keyVar) return parentFor.keyVar; + + const keyExpression = + getChildKey(parentFor) || createIndexKey(parentFor, transformHelper); + const bracketedKeyExpression = builder.binaryExpression( + builder.literal("["), + "+", + builder.binaryExpression(keyExpression, "+", builder.literal("]")) + ); + const varName = "keyscope__" + transformHelper.nextUniqueId(); + const varDeclaration = builder.var(varName, bracketedKeyExpression); + + parentFor.prependChild(varDeclaration); + + return (parentFor.keyVar = builder.identifier(varName)); +}; + +const createIndexKey = (forNode, transformHelper) => { + const context = transformHelper.context; + const builder = context.builder; + const varName = "for__" + transformHelper.nextUniqueId(); + const intialize = builder.parseStatement(`var ${varName} = 0;`); + const parentForKey = getParentForKeyVar(forNode, transformHelper); + + forNode.insertSiblingBefore(intialize); + + let keyExpression = builder.parseExpression(`${varName}++`); + + if (parentForKey) { + keyExpression = builder.binaryExpression( + keyExpression, + "+", + parentForKey + ); + } + + return keyExpression; +}; + +const getParentFor = el => { + let current = el; + while ((current = current.parentNode)) { + if (current.tagName === "for") return current; + } +}; + +const getChildKey = el => { + let current = el.firstChild; + while (current) { + if (current.key) return current.key; + if (current.hasAttribute && current.hasAttribute("key")) { + return current.getAttributeValue("key"); + } + current = current.nextSibling; + } +}; diff --git a/test/compiler/fixtures-html/at-tags-dynamic/expected.js b/test/compiler/fixtures-html/at-tags-dynamic/expected.js index 47ef654da..c23d67c03 100644 --- a/test/compiler/fixtures-html/at-tags-dynamic/expected.js +++ b/test/compiler/fixtures-html/at-tags-dynamic/expected.js @@ -21,7 +21,11 @@ function render(input, out, __component, component, state) { hello_tag(marko_mergeNestedTagsHelper({ renderBody: function renderBody(out, hello0) { + var for__1 = 0; + marko_forEach(input.colors, function(color) { + var keyscope__2 = "[" + ((for__1++) + "]"); + hello_foo_nested_tag({ renderBody: function renderBody(out) { out.w("Foo!"); diff --git a/test/compiler/fixtures-html/simple/expected.js b/test/compiler/fixtures-html/simple/expected.js index 750b201a3..3c28356cf 100644 --- a/test/compiler/fixtures-html/simple/expected.js +++ b/test/compiler/fixtures-html/simple/expected.js @@ -33,7 +33,11 @@ function render(input, out, __component, component, state) { if (input.colors.length) { out.w("
"); _preserve_tag({ bodyOnly: true, - key: __key3, + key: __key5, renderBody: function renderBody(out) { out.w(marko_escapeXml(Date.now())); } @@ -44,7 +48,7 @@ function render(input, out, __component, component, state) { include_tag({ _target: foo_template - }, out, __component, "5"); + }, out, __component, "7"); } marko_template._ = marko_renderer(render, { diff --git a/test/components-compilation/fixtures-html/macro-widget/expected.js b/test/components-compilation/fixtures-html/macro-widget/expected.js index 68ae8e47f..28cd2bfeb 100644 --- a/test/components-compilation/fixtures-html/macro-widget/expected.js +++ b/test/components-compilation/fixtures-html/macro-widget/expected.js @@ -28,11 +28,15 @@ function render(input, out, __component, component, state) { out.w("