diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 2879058d63..ce6c56fc7d 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -791,7 +791,7 @@ export default class Generator { node.generator = generator; - if (node.type === 'Element' && (node.name === ':Component' || node.name === ':Self' || generator.components.has(node.name))) { + if (node.type === 'Element' && (node.name === ':Component' || node.name === ':Self' || node.name === 'svelte:component' || node.name === 'svelte:self' || generator.components.has(node.name))) { node.type = 'Component'; Object.setPrototypeOf(node, nodes.Component.prototype); } else if (node.type === 'Element' && node.name === 'title' && parentIsHead(parent)) { // TODO do this in parse? diff --git a/src/generators/nodes/Component.ts b/src/generators/nodes/Component.ts index 94a186229d..239dc4bed1 100644 --- a/src/generators/nodes/Component.ts +++ b/src/generators/nodes/Component.ts @@ -45,8 +45,8 @@ export default class Component extends Node { this.var = block.getUniqueName( ( - this.name === ':Self' ? this.generator.name : - this.name === ':Component' ? 'switch_instance' : + (this.name === ':Self' || this.name === 'svelte:self') ? this.generator.name : + (this.name === ':Component' || this.name === 'svelte:component') ? 'switch_instance' : this.name ).toLowerCase() ); @@ -386,7 +386,7 @@ export default class Component extends Node { block.builders.destroy.addLine(`if (${name}) ${name}.destroy(false);`); } else { - const expression = this.name === ':Self' + const expression = (this.name === ':Self' || this.name === 'svelte:self') ? generator.name : `%components-${this.name}`; diff --git a/src/generators/server-side-rendering/visitors/Component.ts b/src/generators/server-side-rendering/visitors/Component.ts index cfa0b8b132..d0df39c9be 100644 --- a/src/generators/server-side-rendering/visitors/Component.ts +++ b/src/generators/server-side-rendering/visitors/Component.ts @@ -91,7 +91,7 @@ export default function visitComponent( if (isDynamicComponent) block.contextualise(node.expression); const expression = ( - node.name === ':Self' ? generator.name : + (node.name === ':Self' || node.name === 'svelte:self') ? generator.name : isDynamicComponent ? `((${node.metadata.snippet}) || __missingComponent)` : `%components-${node.name}` ); diff --git a/src/parse/read/directives.ts b/src/parse/read/directives.ts index 744da4a240..77c0ffe47a 100644 --- a/src/parse/read/directives.ts +++ b/src/parse/read/directives.ts @@ -140,20 +140,25 @@ export function readDirective( const expressionStart = parser.index; - if (parser.eat('{{')) { - let message = 'directive values should not be wrapped'; - const expressionEnd = parser.template.indexOf('}}', expressionStart); - if (expressionEnd !== -1) { - const value = parser.template.slice(parser.index, expressionEnd); - message += ` — use '${value}', not '{{${value}}}'`; + try { + expression = readExpression(parser, expressionStart, quoteMark); + if (directive.allowedExpressionTypes.indexOf(expression.type) === -1) { + parser.error(directive.error, expressionStart); + } + } catch (err) { + if (parser.template[expressionStart] === '{') { + // assume the mistake was wrapping the directive arguments. + // this could yield false positives! but hopefully not too many + let message = 'directive values should not be wrapped'; + const expressionEnd = parser.template.indexOf((parser.v2 ? '}' : '}}'), expressionStart); + if (expressionEnd !== -1) { + const value = parser.template.slice(expressionStart + (parser.v2 ? 1 : 2), expressionEnd); + message += ` — use '${value}', not '${parser.v2 ? `{${value}}` : `{{${value}}}`}'`; + } + parser.error(message, expressionStart); } - parser.error(message, expressionStart); - } - - expression = readExpression(parser, expressionStart, quoteMark); - if (directive.allowedExpressionTypes.indexOf(expression.type) === -1) { - parser.error(directive.error, expressionStart); + throw err; } } diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index e29965994d..ba4fce179f 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -10,12 +10,11 @@ import { Node } from '../../interfaces'; const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; -const SELF = ':Self'; -const COMPONENT = ':Component'; - const metaTags = new Set([ ':Window', - ':Head' + ':Head', + 'svelte:window', + 'svelte:head' ]); const specials = new Map([ @@ -85,9 +84,9 @@ export default function tag(parser: Parser) { if (metaTags.has(name)) { if (isClosingTag) { - if (name === ':Window' && parser.current().children.length) { + if ((name === ':Window' || name === 'svelte:window') && parser.current().children.length) { parser.error( - `<:Window> cannot have children`, + `<${name}> cannot have children`, parser.current().children[0].start ); } diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts index 36377f4065..6e2b6b4133 100644 --- a/src/validate/html/index.ts +++ b/src/validate/html/index.ts @@ -33,6 +33,8 @@ export default function validateHtml(validator: Validator, html: Node) { const isComponent = node.name === ':Self' || node.name === ':Component' || + node.name === 'svelte:self' || + node.name === 'svelte:component' || validator.components.has(node.name); validateElement( diff --git a/src/validate/html/validateElement.ts b/src/validate/html/validateElement.ts index 00c195a85c..b7db93488e 100644 --- a/src/validate/html/validateElement.ts +++ b/src/validate/html/validateElement.ts @@ -280,7 +280,7 @@ function checkSlotAttribute(validator: Validator, node: Node, attribute: Node, s const parent = stack[i]; if (parent.type === 'Element') { // if we're inside a component or a custom element, gravy - if (parent.name === ':Self' || parent.name === ':Component' || validator.components.has(parent.name)) return; + if (parent.name === ':Self' || parent.name === ':Component' || parent.name === 'svelte:self' || parent.name === 'svelte:component' || validator.components.has(parent.name)) return; if (/-/.test(parent.name)) return; } diff --git a/test/parser/index.js b/test/parser/index.js index 0bdb4ad740..dd692f8c6f 100644 --- a/test/parser/index.js +++ b/test/parser/index.js @@ -16,56 +16,49 @@ describe('parse', () => { } (solo ? it.only : it)(dir, () => { - const input = fs - .readFileSync(`test/parser/samples/${dir}/input.html`, 'utf-8') - .replace(/\s+$/, ''); - - const input_v2 = fs - .readFileSync(`test/parser/samples/${dir}/input-v2.html`, 'utf-8') - .replace(/\s+$/, ''); - const options = tryToLoadJson(`test/parser/samples/${dir}/options.json`) || {}; - try { - const actual = svelte.parse(input, options); - const expected = require(`./samples/${dir}/output.json`); - - fs.writeFileSync( - `test/parser/samples/${dir}/_actual.json`, - JSON.stringify(actual, null, '\t') - ); - - assert.deepEqual(actual.html, expected.html); - assert.deepEqual(actual.css, expected.css); - assert.deepEqual(actual.js, expected.js); - - // TODO remove v1 tests - const actual_v2 = svelte.parse(input_v2, Object.assign({ parser: 'v2' }, options)); - const expected_v2 = require(`./samples/${dir}/output-v2.json`); - - fs.writeFileSync( - `test/parser/samples/${dir}/_actual-v2.json`, - JSON.stringify(actual_v2, null, '\t') - ); - - assert.deepEqual(actual_v2.html, expected_v2.html); - assert.deepEqual(actual_v2.css, expected_v2.css); - assert.deepEqual(actual_v2.js, expected_v2.js); - } catch (err) { - if (err.name !== 'ParseError') throw err; - + function test(options, input, expectedOutput, expectedError, outputFile) { try { - const expected = require(`./samples/${dir}/error.json`); - - assert.equal(err.message, expected.message); - assert.deepEqual(err.loc, expected.loc); - assert.equal(err.pos, expected.pos); - assert.equal(err.toString().split('\n')[0], `${expected.message} (${expected.loc.line}:${expected.loc.column})`); - } catch (err2) { - const e = err2.code === 'MODULE_NOT_FOUND' ? err : err2; - throw e; + const actual = svelte.parse(input, options); + + fs.writeFileSync(outputFile, JSON.stringify(actual, null, '\t')); + + assert.deepEqual(actual.html, expectedOutput.html); + assert.deepEqual(actual.css, expectedOutput.css); + assert.deepEqual(actual.js, expectedOutput.js); + } catch (err) { + if (err.name !== 'ParseError') throw err; + if (!expectedError) throw err; + + try { + assert.equal(err.message, expectedError.message); + assert.deepEqual(err.loc, expectedError.loc); + assert.equal(err.pos, expectedError.pos); + assert.equal(err.toString().split('\n')[0], `${expectedError.message} (${expectedError.loc.line}:${expectedError.loc.column})`); + } catch (err2) { + const e = err2.code === 'MODULE_NOT_FOUND' ? err : err2; + throw e; + } } } + + // TODO remove v1 tests + test( + options, + fs.readFileSync(`test/parser/samples/${dir}/input.html`, 'utf-8').replace(/\s+$/, ''), + tryToLoadJson(`test/parser/samples/${dir}/output.json`), + tryToLoadJson(`test/parser/samples/${dir}/error.json`), + `test/parser/samples/${dir}/_actual.json` + ); + + test( + Object.assign({ parser: 'v2' }, options), + fs.readFileSync(`test/parser/samples/${dir}/input-v2.html`, 'utf-8').replace(/\s+$/, ''), + tryToLoadJson(`test/parser/samples/${dir}/output-v2.json`), + tryToLoadJson(`test/parser/samples/${dir}/error-v2.json`), + `test/parser/samples/${dir}/_actual-v2.json` + ); }); }); diff --git a/test/parser/samples/attribute-unique-error/error-v2.json b/test/parser/samples/attribute-unique-error/error-v2.json new file mode 100644 index 0000000000..b4ab7a57b5 --- /dev/null +++ b/test/parser/samples/attribute-unique-error/error-v2.json @@ -0,0 +1,8 @@ +{ + "message": "Attributes need to be unique", + "loc": { + "line": 1, + "column": 17 + }, + "pos": 17 +} diff --git a/test/parser/samples/error-binding-disabled/error-v2.json b/test/parser/samples/error-binding-disabled/error-v2.json new file mode 100644 index 0000000000..36ad59d26f --- /dev/null +++ b/test/parser/samples/error-binding-disabled/error-v2.json @@ -0,0 +1,8 @@ +{ + "message": "Two-way binding is disabled", + "loc": { + "line": 1, + "column": 7 + }, + "pos": 7 +} \ No newline at end of file diff --git a/test/parser/samples/error-binding-mustaches/error-v2.json b/test/parser/samples/error-binding-mustaches/error-v2.json new file mode 100644 index 0000000000..92bb2eb1eb --- /dev/null +++ b/test/parser/samples/error-binding-mustaches/error-v2.json @@ -0,0 +1,8 @@ +{ + "message": "directive values should not be wrapped — use 'foo', not '{foo}'", + "loc": { + "line": 1, + "column": 19 + }, + "pos": 19 +} \ No newline at end of file diff --git a/test/parser/samples/error-binding-rvalue/error-v2.json b/test/parser/samples/error-binding-rvalue/error-v2.json new file mode 100644 index 0000000000..63f6711cd1 --- /dev/null +++ b/test/parser/samples/error-binding-rvalue/error-v2.json @@ -0,0 +1,8 @@ +{ + "message": "Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)", + "pos": 19, + "loc": { + "line": 1, + "column": 19 + } +} \ No newline at end of file diff --git a/test/parser/samples/error-comment-unclosed/error-v2.json b/test/parser/samples/error-comment-unclosed/error-v2.json new file mode 100644 index 0000000000..f392e12fa1 --- /dev/null +++ b/test/parser/samples/error-comment-unclosed/error-v2.json @@ -0,0 +1,8 @@ +{ + "message": "comment was left open, expected -->", + "loc": { + "line": 1, + "column": 24 + }, + "pos": 24 +} diff --git a/test/parser/samples/error-css/error-v2.json b/test/parser/samples/error-css/error-v2.json new file mode 100644 index 0000000000..177f6d977f --- /dev/null +++ b/test/parser/samples/error-css/error-v2.json @@ -0,0 +1,8 @@ +{ + "message": "LeftCurlyBracket is expected", + "loc": { + "line": 2, + "column": 16 + }, + "pos": 24 +} diff --git a/test/parser/samples/error-event-handler/error-v2.json b/test/parser/samples/error-event-handler/error-v2.json new file mode 100644 index 0000000000..f5e603b06a --- /dev/null +++ b/test/parser/samples/error-event-handler/error-v2.json @@ -0,0 +1,8 @@ +{ + "message": "Expected a method call", + "loc": { + "line": 1, + "column": 15 + }, + "pos": 15 +} \ No newline at end of file diff --git a/test/parser/samples/error-illegal-expression/error-v2.json b/test/parser/samples/error-illegal-expression/error-v2.json new file mode 100644 index 0000000000..b0b557202c --- /dev/null +++ b/test/parser/samples/error-illegal-expression/error-v2.json @@ -0,0 +1,8 @@ +{ + "message": "Assigning to rvalue", + "loc": { + "line": 1, + "column": 1 + }, + "pos": 1 +} diff --git a/test/parser/samples/error-multiple-styles/error-v2.json b/test/parser/samples/error-multiple-styles/error-v2.json new file mode 100644 index 0000000000..421c5558a7 --- /dev/null +++ b/test/parser/samples/error-multiple-styles/error-v2.json @@ -0,0 +1,8 @@ +{ + "message": "You can only have one top-level