diff --git a/src/compile/Component.ts b/src/compile/Component.ts index bb61ff0506..2e5590c054 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -3,7 +3,7 @@ import { walk, childKeys } from 'estree-walker'; import { getLocator } from 'locate-character'; import Stats from '../Stats'; import reservedNames from '../utils/reservedNames'; -import namespaces from '../utils/namespaces'; +import { namespaces, validNamespaces } from '../utils/namespaces'; import { removeNode } from '../utils/removeNode'; import wrapModule from './wrapModule'; import { createScopes, extractNames, Scope } from '../utils/annotateWithScopes'; @@ -18,6 +18,7 @@ import flattenReference from '../utils/flattenReference'; import addToSet from '../utils/addToSet'; import isReference from 'is-reference'; import TemplateScope from './nodes/shared/TemplateScope'; +import fuzzymatch from '../utils/fuzzymatch'; type Meta = { namespace?: string; @@ -602,42 +603,62 @@ function process_meta(component, nodes) { const meta: Meta = {}; const node = nodes.find(node => node.name === 'svelte:meta'); + function get_value(attribute, message) { + const { name, value } = attribute; + + if (value.length > 1 || (value[0] && value[0].type !== 'Text')) { + component.error(attribute, { + code: `invalid-${name}-attribute`, + message + }); + } + + return value[0].data; + } + if (node) { node.attributes.forEach(attribute => { if (attribute.type === 'Attribute') { - const { name, value } = attribute; - - if (value.length > 1 || (value[0] && value[0].type !== 'Text')) { - component.error(attribute, { - code: `invalid-meta-attribute`, - message: ` cannot have dynamic attributes` - }); - } - - const { data } = value[0]; + const { name } = attribute; switch (name) { case 'tag': - case 'namespace': - if (!data) { + const tag = get_value(attribute, `'tag' must be a string literal`); + + if (!/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { component.error(attribute, { - code: `invalid-${name}-attribute`, - message: ` ${name} attribute must have a string value` + code: `invalid-tag-property`, + message: `tag name must be two or more words joined by the '-' character` }); } - meta[name] = data; + meta.tag = tag; break; - case 'immutable': - if (data && (data !== 'true' && data !== 'false')) { - component.error(attribute, { - code: `invalid-immutable-attribute`, - message: ` immutable attribute must be true or false` - }); + case 'namespace': + const ns = get_value(attribute, `The 'namespace' attribute must be a string literal representing a valid namespace`); + + if (validNamespaces.indexOf(ns) === -1) { + const match = fuzzymatch(ns, validNamespaces); + if (match) { + component.error(attribute, { + code: `invalid-namespace-property`, + message: `Invalid namespace '${ns}' (did you mean '${match}'?)` + }); + } else { + component.error(attribute, { + code: `invalid-namespace-property`, + message: `Invalid namespace '${ns}'` + }); + } } - meta.immutable = data !== 'false'; + meta.namespace = ns; + break; + + case 'immutable': + meta.immutable = get_value(attribute, `immutable attribute must be true or false`) !== 'false'; + break; default: component.error(attribute, { @@ -668,8 +689,6 @@ function process_meta(component, nodes) { message: ` can only have static 'tag', 'namespace' and 'immutable' attributes, or a bind:props directive` }); } - - }); } diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index 7a3e9b340e..63e7173022 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -9,7 +9,7 @@ import Animation from './Animation'; import Action from './Action'; import Class from './Class'; import Text from './Text'; -import * as namespaces from '../../utils/namespaces'; +import { namespaces } from '../../utils/namespaces'; import mapChildren from './shared/mapChildren'; import { dimensions } from '../../utils/patterns'; import fuzzymatch from '../../utils/fuzzymatch'; diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index 358820ce3c..d148cdfb3b 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -10,7 +10,7 @@ import { stringify, escapeHTML, escape } from '../../../../utils/stringify'; import TextWrapper from '../Text'; import fixAttributeCasing from '../../../../utils/fixAttributeCasing'; import deindent from '../../../../utils/deindent'; -import namespaces from '../../../../utils/namespaces'; +import { namespaces } from '../../../../utils/namespaces'; import AttributeWrapper from './Attribute'; import StyleAttributeWrapper from './StyleAttribute'; import { dimensions } from '../../../../utils/patterns'; diff --git a/src/utils/namespaces.ts b/src/utils/namespaces.ts index a038dfa345..bac1797b49 100644 --- a/src/utils/namespaces.ts +++ b/src/utils/namespaces.ts @@ -20,6 +20,4 @@ export const validNamespaces = [ xmlns, ]; -const namespaces: Record = { html, mathml, svg, xlink, xml, xmlns }; - -export default namespaces; +export const namespaces: Record = { html, mathml, svg, xlink, xml, xmlns }; diff --git a/test/validator/samples/namespace-invalid-unguessable/errors.json b/test/validator/samples/namespace-invalid-unguessable/errors.json index badab0d40e..98e95676c1 100644 --- a/test/validator/samples/namespace-invalid-unguessable/errors.json +++ b/test/validator/samples/namespace-invalid-unguessable/errors.json @@ -1,15 +1,15 @@ [{ "code": "invalid-namespace-property", "message": "Invalid namespace 'lol'", - "pos": 29, + "pos": 13, "start": { - "line": 3, - "column": 2, - "character": 29 + "line": 1, + "column": 13, + "character": 13 }, "end": { - "line": 3, - "column": 18, - "character": 45 + "line": 1, + "column": 28, + "character": 28 } }] diff --git a/test/validator/samples/namespace-invalid/errors.json b/test/validator/samples/namespace-invalid/errors.json index 13a547bced..9e99fe7ab1 100644 --- a/test/validator/samples/namespace-invalid/errors.json +++ b/test/validator/samples/namespace-invalid/errors.json @@ -1,15 +1,15 @@ [{ "code": "invalid-namespace-property", "message": "Invalid namespace 'http://www.w3.org/1999/svg' (did you mean 'http://www.w3.org/2000/svg'?)", - "pos": 29, + "pos": 13, "start": { - "line": 3, - "column": 2, - "character": 29 + "line": 1, + "column": 13, + "character": 13 }, "end": { - "line": 3, - "column": 41, - "character": 68 + "line": 1, + "column": 51, + "character": 51 } }] diff --git a/test/validator/samples/namespace-non-literal/errors.json b/test/validator/samples/namespace-non-literal/errors.json index cd98748408..112e2ac73b 100644 --- a/test/validator/samples/namespace-non-literal/errors.json +++ b/test/validator/samples/namespace-non-literal/errors.json @@ -1,15 +1,15 @@ [{ "code": "invalid-namespace-attribute", "message": "The 'namespace' attribute must be a string literal representing a valid namespace", - "pos": 79, + "pos": 13, "start": { - "line": 5, - "column": 2, - "character": 79 + "line": 1, + "column": 13, + "character": 13 }, "end": { - "line": 5, - "column": 11, - "character": 88 + "line": 1, + "column": 27, + "character": 27 } }] diff --git a/test/validator/samples/tag-invalid/errors.json b/test/validator/samples/tag-invalid/errors.json index 842d98df31..6b61f76fbb 100644 --- a/test/validator/samples/tag-invalid/errors.json +++ b/test/validator/samples/tag-invalid/errors.json @@ -2,14 +2,14 @@ "code": "invalid-tag-property", "message": "tag name must be two or more words joined by the '-' character", "start": { - "line": 3, - "column": 7, - "character": 34 + "line": 1, + "column": 13, + "character": 13 }, "end": { - "line": 3, - "column": 16, - "character": 43 + "line": 1, + "column": 26, + "character": 26 }, - "pos": 34 + "pos": 13 }] \ No newline at end of file diff --git a/test/validator/samples/tag-non-string/errors.json b/test/validator/samples/tag-non-string/errors.json index 48048f584e..54d58b2d4e 100644 --- a/test/validator/samples/tag-non-string/errors.json +++ b/test/validator/samples/tag-non-string/errors.json @@ -2,14 +2,14 @@ "code": "invalid-tag-attribute", "message": "'tag' must be a string literal", "start": { - "line": 3, - "column": 7, - "character": 34 + "line": 1, + "column": 13, + "character": 13 }, "end": { - "line": 3, - "column": 9, - "character": 36 + "line": 1, + "column": 21, + "character": 21 }, - "pos": 34 + "pos": 13 }] \ No newline at end of file