diff --git a/src/generate/index.js b/src/generate/index.js
index cb0368548a62..b54b5217a000 100644
--- a/src/generate/index.js
+++ b/src/generate/index.js
@@ -4,6 +4,7 @@ import deindent from '../utils/deindent.js';
import isReference from '../utils/isReference.js';
import counter from './utils/counter.js';
import flattenReference from '../utils/flattenReference.js';
+import namespaces from '../utils/namespaces.js';
import getIntro from './utils/getIntro.js';
import getOutro from './utils/getOutro.js';
import visitors from './visitors/index.js';
@@ -277,9 +278,17 @@ export default function generate ( parsed, source, options, names ) {
});
}
+ let namespace = null;
+ if ( templateProperties.namespace ) {
+ const ns = templateProperties.namespace.value;
+ namespace = namespaces[ ns ] || ns;
+
+ // TODO remove the namespace property from the generated code, it's unused past this point
+ }
+
generator.push({
name: 'renderMainFragment',
- namespace: null,
+ namespace,
target: 'target',
elementDepth: 0,
localElementDepth: 0,
diff --git a/src/utils/namespaces.js b/src/utils/namespaces.js
new file mode 100644
index 000000000000..2ab0b116f058
--- /dev/null
+++ b/src/utils/namespaces.js
@@ -0,0 +1,8 @@
+export const html = 'http://www.w3.org/1999/xhtml';
+export const mathml = 'http://www.w3.org/1998/Math/MathML';
+export const svg = 'http://www.w3.org/2000/svg';
+export const xlink = 'http://www.w3.org/1999/xlink';
+export const xml = 'http://www.w3.org/XML/1998/namespace';
+export const xmlns = 'http://www.w3.org/2000/xmlns';
+
+export default { html, mathml, svg, xlink, xml, xmlns };
diff --git a/src/validate/html/index.js b/src/validate/html/index.js
index d7d7378fbd75..a7feafc42bf0 100644
--- a/src/validate/html/index.js
+++ b/src/validate/html/index.js
@@ -1,13 +1,31 @@
+import * as namespaces from '../../utils/namespaces.js';
+
+const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|title|tref|tspan|unknown|use|view|vkern)$/;
+
export default function validateHtml ( validator, html ) {
+ let elementDepth = 0;
+
function visit ( node ) {
if ( node.type === 'EachBlock' ) {
if ( !~validator.names.indexOf( node.context ) ) validator.names.push( node.context );
if ( node.index && !~validator.names.indexOf( node.index ) ) validator.names.push( node.index );
}
+ if ( node.type === 'Element' ) {
+ if ( elementDepth === 0 && validator.namespace !== namespaces.svg && svg.test( node.name ) ) {
+ validator.warn( `<${node.name}> is an SVG element – did you forget to add { namespace: 'svg' } ?`, node.start );
+ }
+
+ elementDepth += 1;
+ }
+
if ( node.children ) {
node.children.forEach( visit );
}
+
+ if ( node.type === 'Element' ) {
+ elementDepth -= 1;
+ }
}
html.children.forEach( visit );
diff --git a/src/validate/index.js b/src/validate/index.js
index 89811421e5ed..47f440cd4ae8 100644
--- a/src/validate/index.js
+++ b/src/validate/index.js
@@ -40,17 +40,19 @@ export default function validate ( parsed, source, options ) {
templateProperties: {},
- names: []
- };
+ names: [],
- if ( parsed.html ) {
- validateHtml( validator, parsed.html );
- }
+ namespace: null
+ };
if ( parsed.js ) {
validateJs( validator, parsed.js );
}
+ if ( parsed.html ) {
+ validateHtml( validator, parsed.html );
+ }
+
return {
names: validator.names
};
diff --git a/src/validate/js/index.js b/src/validate/js/index.js
index 026a304309be..60e34e111b32 100644
--- a/src/validate/js/index.js
+++ b/src/validate/js/index.js
@@ -2,6 +2,7 @@ import propValidators from './propValidators/index.js';
import FuzzySet from './utils/FuzzySet.js';
import checkForDupes from './utils/checkForDupes.js';
import checkForComputedKeys from './utils/checkForComputedKeys.js';
+import namespaces from '../../utils/namespaces.js';
const validPropList = Object.keys( propValidators );
@@ -29,7 +30,7 @@ export default function validateJs ( validator, js ) {
checkForDupes( validator, validator.defaultExport.declaration.properties );
validator.defaultExport.declaration.properties.forEach( prop => {
- validator.templateProperties[ prop.key.value ] = prop;
+ validator.templateProperties[ prop.key.name ] = prop;
});
validator.defaultExport.declaration.properties.forEach( prop => {
@@ -48,5 +49,10 @@ export default function validateJs ( validator, js ) {
}
}
});
+
+ if ( validator.templateProperties.namespace ) {
+ const ns = validator.templateProperties.namespace.value.value;
+ validator.namespace = namespaces[ ns ] || ns;
+ }
}
}
diff --git a/src/validate/js/propValidators/index.js b/src/validate/js/propValidators/index.js
index dcbf004fffda..655df8d3cc43 100644
--- a/src/validate/js/propValidators/index.js
+++ b/src/validate/js/propValidators/index.js
@@ -6,6 +6,7 @@ import helpers from './helpers.js';
import methods from './methods.js';
import components from './components.js';
import events from './events.js';
+import namespace from './namespace.js';
export default {
data,
@@ -15,5 +16,6 @@ export default {
helpers,
methods,
components,
- events
+ events,
+ namespace
};
diff --git a/src/validate/js/propValidators/namespace.js b/src/validate/js/propValidators/namespace.js
new file mode 100644
index 000000000000..ca664261ef60
--- /dev/null
+++ b/src/validate/js/propValidators/namespace.js
@@ -0,0 +1,5 @@
+export default function namespace ( validator, prop ) {
+ if ( prop.value.type !== 'Literal' || typeof prop.value.value !== 'string' ) {
+ validator.error( `The 'namespace' property must be a string literal representing a valid namespace`, prop.start );
+ }
+}
diff --git a/test/generator/svg-child-component-declared-namespace-shorthand/Rect.html b/test/generator/svg-child-component-declared-namespace-shorthand/Rect.html
new file mode 100644
index 000000000000..c158d7fdf57e
--- /dev/null
+++ b/test/generator/svg-child-component-declared-namespace-shorthand/Rect.html
@@ -0,0 +1,7 @@
+
+
+
diff --git a/test/generator/svg-child-component/_config.js b/test/generator/svg-child-component-declared-namespace-shorthand/_config.js
similarity index 97%
rename from test/generator/svg-child-component/_config.js
rename to test/generator/svg-child-component-declared-namespace-shorthand/_config.js
index 977b02407d4c..2944111fd9e1 100644
--- a/test/generator/svg-child-component/_config.js
+++ b/test/generator/svg-child-component-declared-namespace-shorthand/_config.js
@@ -1,12 +1,13 @@
export default {
- skip: true,
data: {
x: 0,
y: 0,
width: 100,
height: 100
},
+
html: ``,
+
test ( assert, component, target ) {
const svg = target.querySelector( 'svg' );
const rect = target.querySelector( 'rect' );
diff --git a/test/generator/svg-child-component/main.html b/test/generator/svg-child-component-declared-namespace-shorthand/main.html
similarity index 99%
rename from test/generator/svg-child-component/main.html
rename to test/generator/svg-child-component-declared-namespace-shorthand/main.html
index 226687a3d1d4..7bef9787b0f1 100644
--- a/test/generator/svg-child-component/main.html
+++ b/test/generator/svg-child-component-declared-namespace-shorthand/main.html
@@ -1,6 +1,7 @@
+
diff --git a/test/generator/svg-child-component-declared-namespace/_config.js b/test/generator/svg-child-component-declared-namespace/_config.js
new file mode 100644
index 000000000000..2944111fd9e1
--- /dev/null
+++ b/test/generator/svg-child-component-declared-namespace/_config.js
@@ -0,0 +1,21 @@
+export default {
+ data: {
+ x: 0,
+ y: 0,
+ width: 100,
+ height: 100
+ },
+
+ html: ``,
+
+ test ( assert, component, target ) {
+ const svg = target.querySelector( 'svg' );
+ const rect = target.querySelector( 'rect' );
+
+ assert.equal( svg.namespaceURI, 'http://www.w3.org/2000/svg' );
+ assert.equal( rect.namespaceURI, 'http://www.w3.org/2000/svg' );
+
+ component.set({ width: 150, height: 50 });
+ assert.equal( target.innerHTML, `` );
+ }
+};
diff --git a/test/generator/svg-child-component-declared-namespace/main.html b/test/generator/svg-child-component-declared-namespace/main.html
new file mode 100644
index 000000000000..7bef9787b0f1
--- /dev/null
+++ b/test/generator/svg-child-component-declared-namespace/main.html
@@ -0,0 +1,11 @@
+
+
+
diff --git a/test/validator/svg-child-component-declared-namespace/input.html b/test/validator/svg-child-component-declared-namespace/input.html
new file mode 100644
index 000000000000..c158d7fdf57e
--- /dev/null
+++ b/test/validator/svg-child-component-declared-namespace/input.html
@@ -0,0 +1,7 @@
+
+
+
diff --git a/test/validator/svg-child-component-declared-namespace/warnings.json b/test/validator/svg-child-component-declared-namespace/warnings.json
new file mode 100644
index 000000000000..fe51488c7066
--- /dev/null
+++ b/test/validator/svg-child-component-declared-namespace/warnings.json
@@ -0,0 +1 @@
+[]
diff --git a/test/generator/svg-child-component/Rect.html b/test/validator/svg-child-component-undeclared-namespace/input.html
similarity index 100%
rename from test/generator/svg-child-component/Rect.html
rename to test/validator/svg-child-component-undeclared-namespace/input.html
diff --git a/test/validator/svg-child-component-undeclared-namespace/warnings.json b/test/validator/svg-child-component-undeclared-namespace/warnings.json
new file mode 100644
index 000000000000..6faf58a4570a
--- /dev/null
+++ b/test/validator/svg-child-component-undeclared-namespace/warnings.json
@@ -0,0 +1,8 @@
+[{
+ "message": " is an SVG element – did you forget to add { namespace: 'svg' } ?",
+ "loc": {
+ "line": 1,
+ "column": 0
+ },
+ "pos": 0
+}]