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 +}]