[feat] Add errorMode option to compile to allow continuing on errors (and mark them as warnings) (#6194)

This PR adds a new option errorMode to CompileOptions to allow continuing the compilation process when errors occured.
When set to warn, this new option will indicate to Svelte that it should log errors as warnings and continue compilation.

This allows (notably) preprocessors to compile the markup to detect vars in markup before preprocessing (in this case: script and style tags are stripped so it can produce a lot of errors).

This PR is part of a work on the svelte-preprocess side to improve the preprocessing of TypeScript files: https://github.com/sveltejs/svelte-preprocess/issues/318

- allow compiler to pass error as warnings
- enforce stops after errors during compilation (for type-checking, TS doesn't know the error method throws)
- should review Element.ts:302
- added a test case for errorMode
- added documentation
pull/6549/head
Maxime LUCE 3 years ago committed by GitHub
parent 931738e821
commit 0cf5511ae0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -45,6 +45,7 @@ The following options can be passed to the compiler. None are required:
| `name` | string | `"Component"` | `name` | string | `"Component"`
| `format` | `"esm"` or `"cjs"` | `"esm"` | `format` | `"esm"` or `"cjs"` | `"esm"`
| `generate` | `"dom"` or `"ssr"` or `false` | `"dom"` | `generate` | `"dom"` or `"ssr"` or `false` | `"dom"`
| `errorMode` | `"throw"` or `"warn"` | `"throw"`
| `varsReport` | `"strict"` or `"full"` or `false` | `"strict"` | `varsReport` | `"strict"` or `"full"` or `false` | `"strict"`
| `dev` | boolean | `false` | `dev` | boolean | `false`
| `immutable` | boolean | `false` | `immutable` | boolean | `false`
@ -67,6 +68,7 @@ The following options can be passed to the compiler. None are required:
| `name` | `"Component"` | `string` that sets the name of the resulting JavaScript class (though the compiler will rename it if it would otherwise conflict with other variables in scope). It will normally be inferred from `filename`. | `name` | `"Component"` | `string` that sets the name of the resulting JavaScript class (though the compiler will rename it if it would otherwise conflict with other variables in scope). It will normally be inferred from `filename`.
| `format` | `"esm"` | If `"esm"`, creates a JavaScript module (with `import` and `export`). If `"cjs"`, creates a CommonJS module (with `require` and `module.exports`), which is useful in some server-side rendering situations or for testing. | `format` | `"esm"` | If `"esm"`, creates a JavaScript module (with `import` and `export`). If `"cjs"`, creates a CommonJS module (with `require` and `module.exports`), which is useful in some server-side rendering situations or for testing.
| `generate` | `"dom"` | If `"dom"`, Svelte emits a JavaScript class for mounting to the DOM. If `"ssr"`, Svelte emits an object with a `render` method suitable for server-side rendering. If `false`, no JavaScript or CSS is returned; just metadata. | `generate` | `"dom"` | If `"dom"`, Svelte emits a JavaScript class for mounting to the DOM. If `"ssr"`, Svelte emits an object with a `render` method suitable for server-side rendering. If `false`, no JavaScript or CSS is returned; just metadata.
| `errorMode` | `"throw"` | If `"throw"`, Svelte throws when a compilation error occured. If `"warn"`, Svelte will treat errors as warnings and add them to the warning report.
| `varsReport` | `"strict"` | If `"strict"`, Svelte returns a variables report with only variables that are not globals nor internals. If `"full"`, Svelte returns a variables report with all detected variables. If `false`, no variables report is returned. | `varsReport` | `"strict"` | If `"strict"`, Svelte returns a variables report with only variables that are not globals nor internals. If `"full"`, Svelte returns a variables report with all detected variables. If `false`, no variables report is returned.
| `dev` | `false` | If `true`, causes extra code to be added to components that will perform runtime checks and provide debugging information during development. | `dev` | `false` | If `true`, causes extra code to be added to components that will perform runtime checks and provide debugging information during development.
| `immutable` | `false` | If `true`, tells the compiler that you promise not to mutate any objects. This allows it to be less conservative about checking whether values have changed. | `immutable` | `false` | If `true`, tells the compiler that you promise not to mutate any objects. This allows it to be less conservative about checking whether values have changed.

@ -435,6 +435,9 @@ export default class Component {
message: string; message: string;
} }
) { ) {
if (this.compile_options.errorMode === 'warn') {
this.warn(pos, e);
} else {
error(e.message, { error(e.message, {
name: 'ValidationError', name: 'ValidationError',
code: e.code, code: e.code,
@ -444,6 +447,7 @@ export default class Component {
filename: this.compile_options.filename filename: this.compile_options.filename
}); });
} }
}
warn( warn(
pos: { pos: {
@ -491,12 +495,12 @@ export default class Component {
private _extract_exports(node) { private _extract_exports(node) {
if (node.type === 'ExportDefaultDeclaration') { if (node.type === 'ExportDefaultDeclaration') {
this.error(node, compiler_errors.default_export); return this.error(node, compiler_errors.default_export);
} }
if (node.type === 'ExportNamedDeclaration') { if (node.type === 'ExportNamedDeclaration') {
if (node.source) { if (node.source) {
this.error(node, compiler_errors.not_implemented); return this.error(node, compiler_errors.not_implemented);
} }
if (node.declaration) { if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') { if (node.declaration.type === 'VariableDeclaration') {
@ -566,7 +570,7 @@ export default class Component {
scope.declarations.forEach((node, name) => { scope.declarations.forEach((node, name) => {
if (name[0] === '$') { if (name[0] === '$') {
this.error(node as any, compiler_errors.illegal_declaration); return this.error(node as any, compiler_errors.illegal_declaration);
} }
const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let');
@ -581,7 +585,7 @@ export default class Component {
globals.forEach((node, name) => { globals.forEach((node, name) => {
if (name[0] === '$') { if (name[0] === '$') {
this.error(node as any, compiler_errors.illegal_subscription); return this.error(node as any, compiler_errors.illegal_subscription);
} else { } else {
this.add_var({ this.add_var({
name, name,
@ -639,7 +643,7 @@ export default class Component {
instance_scope.declarations.forEach((node, name) => { instance_scope.declarations.forEach((node, name) => {
if (name[0] === '$') { if (name[0] === '$') {
this.error(node as any, compiler_errors.illegal_declaration); return this.error(node as any, compiler_errors.illegal_declaration);
} }
const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let');
@ -673,7 +677,7 @@ export default class Component {
}); });
} else if (name[0] === '$') { } else if (name[0] === '$') {
if (name === '$' || name[1] === '$') { if (name === '$' || name[1] === '$') {
this.error(node as any, compiler_errors.illegal_global(name)); return this.error(node as any, compiler_errors.illegal_global(name));
} }
this.add_var({ this.add_var({
@ -870,7 +874,7 @@ export default class Component {
if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) { if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) {
if (!((/Function/.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) { if (!((/Function/.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) {
this.error(node as any, compiler_errors.contextual_store); return this.error(node as any, compiler_errors.contextual_store);
} }
} }
} }
@ -935,7 +939,7 @@ export default class Component {
if (variable.export_name) { if (variable.export_name) {
// TODO is this still true post-#3539? // TODO is this still true post-#3539?
component.error(declarator as any, compiler_errors.destructured_prop); return component.error(declarator as any, compiler_errors.destructured_prop);
} }
if (variable.subscribable) { if (variable.subscribable) {
@ -1300,7 +1304,7 @@ export default class Component {
if (cycle && cycle.length) { if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]); const declarationList = lookup.get(cycle[0]);
const declaration = declarationList[0]; const declaration = declarationList[0];
this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle)); return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle));
} }
const add_declaration = declaration => { const add_declaration = declaration => {
@ -1323,7 +1327,7 @@ export default class Component {
warn_if_undefined(name: string, node, template_scope: TemplateScope) { warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') { if (name[0] === '$') {
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) { if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {
this.error(node, compiler_errors.illegal_global(name)); return this.error(node, compiler_errors.illegal_global(name));
} }
this.has_reactive_assignments = true; // TODO does this belong here? this.has_reactive_assignments = true; // TODO does this belong here?
@ -1372,13 +1376,13 @@ function process_component_options(component: Component, nodes) {
if (!chunk) return true; if (!chunk) return true;
if (value.length > 1) { if (value.length > 1) {
component.error(attribute, { code, message }); return component.error(attribute, { code, message });
} }
if (chunk.type === 'Text') return chunk.data; if (chunk.type === 'Text') return chunk.data;
if (chunk.expression.type !== 'Literal') { if (chunk.expression.type !== 'Literal') {
component.error(attribute, { code, message }); return component.error(attribute, { code, message });
} }
return chunk.expression.value; return chunk.expression.value;
@ -1394,11 +1398,11 @@ function process_component_options(component: Component, nodes) {
const tag = get_value(attribute, compiler_errors.invalid_tag_attribute); const tag = get_value(attribute, compiler_errors.invalid_tag_attribute);
if (typeof tag !== 'string' && tag !== null) { if (typeof tag !== 'string' && tag !== null) {
component.error(attribute, compiler_errors.invalid_tag_attribute); return component.error(attribute, compiler_errors.invalid_tag_attribute);
} }
if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
component.error(attribute, compiler_errors.invalid_tag_property); return component.error(attribute, compiler_errors.invalid_tag_property);
} }
if (tag && !component.compile_options.customElement) { if (tag && !component.compile_options.customElement) {
@ -1413,12 +1417,12 @@ function process_component_options(component: Component, nodes) {
const ns = get_value(attribute, compiler_errors.invalid_namespace_attribute); const ns = get_value(attribute, compiler_errors.invalid_namespace_attribute);
if (typeof ns !== 'string') { if (typeof ns !== 'string') {
component.error(attribute, compiler_errors.invalid_namespace_attribute); return component.error(attribute, compiler_errors.invalid_namespace_attribute);
} }
if (valid_namespaces.indexOf(ns) === -1) { if (valid_namespaces.indexOf(ns) === -1) {
const match = fuzzymatch(ns, valid_namespaces); const match = fuzzymatch(ns, valid_namespaces);
component.error(attribute, compiler_errors.invalid_namespace_property(ns, match)); return component.error(attribute, compiler_errors.invalid_namespace_property(ns, match));
} }
component_options.namespace = ns; component_options.namespace = ns;
@ -1431,7 +1435,7 @@ function process_component_options(component: Component, nodes) {
const value = get_value(attribute, compiler_errors.invalid_attribute_value(name)); const value = get_value(attribute, compiler_errors.invalid_attribute_value(name));
if (typeof value !== 'boolean') { if (typeof value !== 'boolean') {
component.error(attribute, compiler_errors.invalid_attribute_value(name)); return component.error(attribute, compiler_errors.invalid_attribute_value(name));
} }
component_options[name] = value; component_options[name] = value;
@ -1439,10 +1443,10 @@ function process_component_options(component: Component, nodes) {
} }
default: default:
component.error(attribute, compiler_errors.invalid_options_attribute_unknown); return component.error(attribute, compiler_errors.invalid_options_attribute_unknown);
} }
} else { } else {
component.error(attribute, compiler_errors.invalid_options_attribute); return component.error(attribute, compiler_errors.invalid_options_attribute);
} }
}); });
} }

@ -138,7 +138,7 @@ export default class Selector {
for (let i = start; i < end; i += 1) { for (let i = start; i < end; i += 1) {
if (this.blocks[i].global) { if (this.blocks[i].global) {
component.error(this.blocks[i].selectors[0], compiler_errors.css_invalid_global); return component.error(this.blocks[i].selectors[0], compiler_errors.css_invalid_global);
} }
} }

@ -14,6 +14,7 @@ const valid_options = [
'filename', 'filename',
'sourcemap', 'sourcemap',
'generate', 'generate',
'errorMode',
'varsReport', 'varsReport',
'outputFilename', 'outputFilename',
'cssOutputFilename', 'cssOutputFilename',

@ -22,12 +22,14 @@ export default class Animation extends Node {
if (parent.animation) { if (parent.animation) {
component.error(this, compiler_errors.duplicate_animation); component.error(this, compiler_errors.duplicate_animation);
return;
} }
const block = parent.parent; const block = parent.parent;
if (!block || block.type !== 'EachBlock' || !block.key) { if (!block || block.type !== 'EachBlock' || !block.key) {
// TODO can we relax the 'immediate child' rule? // TODO can we relax the 'immediate child' rule?
component.error(this, compiler_errors.invalid_animation_immediate); component.error(this, compiler_errors.invalid_animation_immediate);
return;
} }
(block as EachBlock).has_animation = true; (block as EachBlock).has_animation = true;

@ -37,6 +37,7 @@ export default class Binding extends Node {
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') { if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {
component.error(info, compiler_errors.invalid_directive_value); component.error(info, compiler_errors.invalid_directive_value);
return;
} }
this.name = info.name; this.name = info.name;
@ -50,9 +51,11 @@ export default class Binding extends Node {
// make sure we track this as a mutable ref // make sure we track this as a mutable ref
if (scope.is_let(name)) { if (scope.is_let(name)) {
component.error(this, compiler_errors.invalid_binding_let); component.error(this, compiler_errors.invalid_binding_let);
return;
} else if (scope.names.has(name)) { } else if (scope.names.has(name)) {
if (scope.is_await(name)) { if (scope.is_await(name)) {
component.error(this, compiler_errors.invalid_binding_await); component.error(this, compiler_errors.invalid_binding_await);
return;
} }
scope.dependencies_for_name.get(name).forEach(name => { scope.dependencies_for_name.get(name).forEach(name => {
@ -66,12 +69,14 @@ export default class Binding extends Node {
if (!variable || variable.global) { if (!variable || variable.global) {
component.error(this.expression.node as any, compiler_errors.binding_undeclared(name)); component.error(this.expression.node as any, compiler_errors.binding_undeclared(name));
return;
} }
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true; variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
if (info.expression.type === 'Identifier' && !variable.writable) { if (info.expression.type === 'Identifier' && !variable.writable) {
component.error(this.expression.node as any, compiler_errors.invalid_binding_writibale); component.error(this.expression.node as any, compiler_errors.invalid_binding_writibale);
return;
} }
} }

@ -63,6 +63,7 @@ export default class EachBlock extends AbstractBlock {
if (this.children.length !== 1) { if (this.children.length !== 1) {
const child = this.children.find(child => !!(child as Element).animation); const child = this.children.find(child => !!(child as Element).animation);
component.error((child as Element).animation, compiler_errors.invalid_animation_sole); component.error((child as Element).animation, compiler_errors.invalid_animation_sole);
return;
} }
} }

@ -142,6 +142,7 @@ export default class Element extends Node {
const value_attribute = info.attributes.find(node => node.name === 'value'); const value_attribute = info.attributes.find(node => node.name === 'value');
if (value_attribute) { if (value_attribute) {
component.error(value_attribute, compiler_errors.textarea_duplicate_value); component.error(value_attribute, compiler_errors.textarea_duplicate_value);
return;
} }
// this is an egregious hack, but it's the easiest way to get <textarea> // this is an egregious hack, but it's the easiest way to get <textarea>
@ -272,22 +273,23 @@ export default class Element extends Node {
// Errors // Errors
if (/(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/.test(name)) { if (/(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/.test(name)) {
component.error(attribute, compiler_errors.illegal_attribute(name)); return component.error(attribute, compiler_errors.illegal_attribute(name));
} }
if (name === 'slot') { if (name === 'slot') {
if (!attribute.is_static) { if (!attribute.is_static) {
component.error(attribute, compiler_errors.invalid_slot_attribute); return component.error(attribute, compiler_errors.invalid_slot_attribute);
} }
if (component.slot_outlets.has(name)) { if (component.slot_outlets.has(name)) {
component.error(attribute, compiler_errors.duplicate_slot_attribute(name)); return component.error(attribute, compiler_errors.duplicate_slot_attribute(name));
component.slot_outlets.add(name); // this code was unreachable. Still needed?
// component.slot_outlets.add(name);
} }
if (!(parent.type === 'SlotTemplate' || within_custom_element(parent))) { if (!(parent.type === 'SlotTemplate' || within_custom_element(parent))) {
component.error(attribute, compiler_errors.invalid_slotted_content); return component.error(attribute, compiler_errors.invalid_slotted_content);
} }
} }
@ -519,7 +521,7 @@ export default class Element extends Node {
validate_bindings_foreign() { validate_bindings_foreign() {
this.bindings.forEach(binding => { this.bindings.forEach(binding => {
if (binding.name !== 'this') { if (binding.name !== 'this') {
this.component.error(binding, compiler_errors.invalid_binding_foreign(binding.name)); return this.component.error(binding, compiler_errors.invalid_binding_foreign(binding.name));
} }
}); });
} }
@ -535,13 +537,13 @@ export default class Element extends Node {
if (!attribute) return null; if (!attribute) return null;
if (!attribute.is_static) { if (!attribute.is_static) {
component.error(attribute, compiler_errors.invalid_type); return component.error(attribute, compiler_errors.invalid_type);
} }
const value = attribute.get_static_value(); const value = attribute.get_static_value();
if (value === true) { if (value === true) {
component.error(attribute, compiler_errors.missing_type); return component.error(attribute, compiler_errors.missing_type);
} }
return value; return value;
@ -556,7 +558,7 @@ export default class Element extends Node {
this.name !== 'textarea' && this.name !== 'textarea' &&
this.name !== 'select' this.name !== 'select'
) { ) {
component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'value')); return component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'value'));
} }
if (this.name === 'select') { if (this.name === 'select') {
@ -565,45 +567,45 @@ export default class Element extends Node {
); );
if (attribute && !attribute.is_static) { if (attribute && !attribute.is_static) {
component.error(attribute, compiler_errors.dynamic_multiple_attribute); return component.error(attribute, compiler_errors.dynamic_multiple_attribute);
} }
} else { } else {
check_type_attribute(); check_type_attribute();
} }
} else if (name === 'checked' || name === 'indeterminate') { } else if (name === 'checked' || name === 'indeterminate') {
if (this.name !== 'input') { if (this.name !== 'input') {
component.error(binding, compiler_errors.invalid_binding_elements(this.name, name)); return component.error(binding, compiler_errors.invalid_binding_elements(this.name, name));
} }
const type = check_type_attribute(); const type = check_type_attribute();
if (type !== 'checkbox') { if (type !== 'checkbox') {
component.error(binding, compiler_errors.invalid_binding_no_checkbox(name, type === 'radio')); return component.error(binding, compiler_errors.invalid_binding_no_checkbox(name, type === 'radio'));
} }
} else if (name === 'group') { } else if (name === 'group') {
if (this.name !== 'input') { if (this.name !== 'input') {
component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'group')); return component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'group'));
} }
const type = check_type_attribute(); const type = check_type_attribute();
if (type !== 'checkbox' && type !== 'radio') { if (type !== 'checkbox' && type !== 'radio') {
component.error(binding, compiler_errors.invalid_binding_element_with('<input type="checkbox"> or <input type="radio">', 'group')); return component.error(binding, compiler_errors.invalid_binding_element_with('<input type="checkbox"> or <input type="radio">', 'group'));
} }
} else if (name === 'files') { } else if (name === 'files') {
if (this.name !== 'input') { if (this.name !== 'input') {
component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'files')); return component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'files'));
} }
const type = check_type_attribute(); const type = check_type_attribute();
if (type !== 'file') { if (type !== 'file') {
component.error(binding, compiler_errors.invalid_binding_element_with('<input type="file">', 'files')); return component.error(binding, compiler_errors.invalid_binding_element_with('<input type="file">', 'files'));
} }
} else if (name === 'open') { } else if (name === 'open') {
if (this.name !== 'details') { if (this.name !== 'details') {
component.error(binding, compiler_errors.invalid_binding_element_with('<details>', name)); return component.error(binding, compiler_errors.invalid_binding_element_with('<details>', name));
} }
} else if ( } else if (
name === 'currentTime' || name === 'currentTime' ||
@ -619,22 +621,22 @@ export default class Element extends Node {
name === 'ended' name === 'ended'
) { ) {
if (this.name !== 'audio' && this.name !== 'video') { if (this.name !== 'audio' && this.name !== 'video') {
component.error(binding, compiler_errors.invalid_binding_element_with('audio> or <video>', name)); return component.error(binding, compiler_errors.invalid_binding_element_with('audio> or <video>', name));
} }
} else if ( } else if (
name === 'videoHeight' || name === 'videoHeight' ||
name === 'videoWidth' name === 'videoWidth'
) { ) {
if (this.name !== 'video') { if (this.name !== 'video') {
component.error(binding, compiler_errors.invalid_binding_element_with('<video>', name)); return component.error(binding, compiler_errors.invalid_binding_element_with('<video>', name));
} }
} else if (dimensions.test(name)) { } else if (dimensions.test(name)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) { if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
component.error(binding, compiler_errors.invalid_binding_on(binding.name, `<svg>. Use '${name.replace('offset', 'client')}' instead`)); return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `<svg>. Use '${name.replace('offset', 'client')}' instead`));
} else if (svg.test(this.name)) { } else if (svg.test(this.name)) {
component.error(binding, compiler_errors.invalid_binding_on(binding.name, 'SVG elements')); return component.error(binding, compiler_errors.invalid_binding_on(binding.name, 'SVG elements'));
} else if (is_void(this.name)) { } else if (is_void(this.name)) {
component.error(binding, compiler_errors.invalid_binding_on(binding.name, `void elements like <${this.name}>. Use a wrapper element instead`)); return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `void elements like <${this.name}>. Use a wrapper element instead`));
} }
} else if ( } else if (
name === 'textContent' || name === 'textContent' ||
@ -645,12 +647,12 @@ export default class Element extends Node {
); );
if (!contenteditable) { if (!contenteditable) {
component.error(binding, compiler_errors.missing_contenteditable_attribute); return component.error(binding, compiler_errors.missing_contenteditable_attribute);
} else if (contenteditable && !contenteditable.is_static) { } else if (contenteditable && !contenteditable.is_static) {
component.error(contenteditable, compiler_errors.dynamic_contenteditable_attribute); return component.error(contenteditable, compiler_errors.dynamic_contenteditable_attribute);
} }
} else if (name !== 'this') { } else if (name !== 'this') {
component.error(binding, compiler_errors.invalid_binding(binding.name)); return component.error(binding, compiler_errors.invalid_binding(binding.name));
} }
}); });
} }
@ -672,16 +674,16 @@ export default class Element extends Node {
this.handlers.forEach(handler => { this.handlers.forEach(handler => {
if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) { if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) {
component.error(handler, compiler_errors.invalid_event_modifier_combination('passive', 'preventDefault')); return component.error(handler, compiler_errors.invalid_event_modifier_combination('passive', 'preventDefault'));
} }
if (handler.modifiers.has('passive') && handler.modifiers.has('nonpassive')) { if (handler.modifiers.has('passive') && handler.modifiers.has('nonpassive')) {
component.error(handler, compiler_errors.invalid_event_modifier_combination('passive', 'nonpassive')); return component.error(handler, compiler_errors.invalid_event_modifier_combination('passive', 'nonpassive'));
} }
handler.modifiers.forEach(modifier => { handler.modifiers.forEach(modifier => {
if (!valid_modifiers.has(modifier)) { if (!valid_modifiers.has(modifier)) {
component.error(handler, compiler_errors.invalid_event_modifier(list(Array.from(valid_modifiers)))); return component.error(handler, compiler_errors.invalid_event_modifier(list(Array.from(valid_modifiers))));
} }
if (modifier === 'passive') { if (modifier === 'passive') {
@ -697,7 +699,7 @@ export default class Element extends Node {
if (component.compile_options.legacy && (modifier === 'once' || modifier === 'passive')) { if (component.compile_options.legacy && (modifier === 'once' || modifier === 'passive')) {
// TODO this could be supported, but it would need a few changes to // TODO this could be supported, but it would need a few changes to
// how event listeners work // how event listeners work
component.error(handler, compiler_errors.invalid_event_modifier_legacy(modifier)); return component.error(handler, compiler_errors.invalid_event_modifier_legacy(modifier));
} }
}); });

@ -16,6 +16,7 @@ export default class Head extends Node {
if (info.attributes.length) { if (info.attributes.length) {
component.error(info.attributes[0], compiler_errors.invalid_attribute_head); component.error(info.attributes[0], compiler_errors.invalid_attribute_head);
return;
} }
this.children = map_children(component, parent, scope, info.children.filter(child => { this.children = map_children(component, parent, scope, info.children.filter(child => {

@ -42,7 +42,7 @@ export default class InlineComponent extends Node {
/* eslint-disable no-fallthrough */ /* eslint-disable no-fallthrough */
switch (node.type) { switch (node.type) {
case 'Action': case 'Action':
component.error(node, compiler_errors.invalid_action); return component.error(node, compiler_errors.invalid_action);
case 'Attribute': case 'Attribute':
if (node.name.startsWith('--')) { if (node.name.startsWith('--')) {
@ -59,7 +59,7 @@ export default class InlineComponent extends Node {
break; break;
case 'Class': case 'Class':
component.error(node, compiler_errors.invalid_class); return component.error(node, compiler_errors.invalid_class);
case 'EventHandler': case 'EventHandler':
this.handlers.push(new EventHandler(component, this, scope, node)); this.handlers.push(new EventHandler(component, this, scope, node));
@ -70,7 +70,7 @@ export default class InlineComponent extends Node {
break; break;
case 'Transition': case 'Transition':
component.error(node, compiler_errors.invalid_transition); return component.error(node, compiler_errors.invalid_transition);
default: default:
throw new Error(`Not implemented: ${node.type}`); throw new Error(`Not implemented: ${node.type}`);
@ -95,7 +95,7 @@ export default class InlineComponent extends Node {
this.handlers.forEach(handler => { this.handlers.forEach(handler => {
handler.modifiers.forEach(modifier => { handler.modifiers.forEach(modifier => {
if (modifier !== 'once') { if (modifier !== 'once') {
component.error(handler, compiler_errors.invalid_event_modifier_component); return component.error(handler, compiler_errors.invalid_event_modifier_component);
} }
}); });
}); });

@ -27,7 +27,7 @@ export default class Let extends Node {
walk(info.expression, { walk(info.expression, {
enter(node: Identifier|BasePattern) { enter(node: Identifier|BasePattern) {
if (!applicable.has(node.type)) { if (!applicable.has(node.type)) {
component.error(node as any, compiler_errors.invalid_let); return component.error(node as any, compiler_errors.invalid_let);
} }
if (node.type === 'Identifier') { if (node.type === 'Identifier') {

@ -18,17 +18,17 @@ export default class Slot extends Element {
info.attributes.forEach(attr => { info.attributes.forEach(attr => {
if (attr.type !== 'Attribute' && attr.type !== 'Spread') { if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
component.error(attr, compiler_errors.invalid_slot_directive); return component.error(attr, compiler_errors.invalid_slot_directive);
} }
if (attr.name === 'name') { if (attr.name === 'name') {
if (attr.value.length !== 1 || attr.value[0].type !== 'Text') { if (attr.value.length !== 1 || attr.value[0].type !== 'Text') {
component.error(attr, compiler_errors.dynamic_slot_name); return component.error(attr, compiler_errors.dynamic_slot_name);
} }
this.slot_name = attr.value[0].data; this.slot_name = attr.value[0].data;
if (this.slot_name === 'default') { if (this.slot_name === 'default') {
component.error(attr, compiler_errors.invalid_slot_name); return component.error(attr, compiler_errors.invalid_slot_name);
} }
} }

@ -46,11 +46,11 @@ export default class SlotTemplate extends Node {
if (node.name === 'slot') { if (node.name === 'slot') {
this.slot_attribute = new Attribute(component, this, scope, node); this.slot_attribute = new Attribute(component, this, scope, node);
if (!this.slot_attribute.is_static) { if (!this.slot_attribute.is_static) {
component.error(node, compiler_errors.invalid_slot_attribute); return component.error(node, compiler_errors.invalid_slot_attribute);
} }
const value = this.slot_attribute.get_static_value(); const value = this.slot_attribute.get_static_value();
if (typeof value === 'boolean') { if (typeof value === 'boolean') {
component.error(node, compiler_errors.invalid_slot_attribute_value_missing); return component.error(node, compiler_errors.invalid_slot_attribute_value_missing);
} }
this.slot_template_name = value as string; this.slot_template_name = value as string;
break; break;
@ -68,7 +68,7 @@ export default class SlotTemplate extends Node {
validate_slot_template_placement() { validate_slot_template_placement() {
if (this.parent.type !== 'InlineComponent') { if (this.parent.type !== 'InlineComponent') {
this.component.error(this, compiler_errors.invalid_slotted_content_fragment); return this.component.error(this, compiler_errors.invalid_slotted_content_fragment);
} }
} }
} }

@ -16,11 +16,12 @@ export default class Title extends Node {
if (info.attributes.length > 0) { if (info.attributes.length > 0) {
component.error(info.attributes[0], compiler_errors.illegal_attribute_title); component.error(info.attributes[0], compiler_errors.illegal_attribute_title);
return;
} }
info.children.forEach(child => { info.children.forEach(child => {
if (child.type !== 'Text' && child.type !== 'MustacheTag') { if (child.type !== 'Text' && child.type !== 'MustacheTag') {
component.error(child, compiler_errors.illegal_structure_title); return component.error(child, compiler_errors.illegal_structure_title);
} }
}); });

@ -27,6 +27,7 @@ export default class Transition extends Node {
if ((info.intro && parent.intro) || (info.outro && parent.outro)) { if ((info.intro && parent.intro) || (info.outro && parent.outro)) {
const parent_transition = (parent.intro || parent.outro); const parent_transition = (parent.intro || parent.outro);
component.error(info, compiler_errors.duplicate_transition(this.directive, parent_transition.directive)); component.error(info, compiler_errors.duplicate_transition(this.directive, parent_transition.directive));
return;
} }
this.expression = info.expression this.expression = info.expression

@ -37,7 +37,7 @@ export default class Window extends Node {
const { parts } = flatten_reference(node.expression); const { parts } = flatten_reference(node.expression);
// TODO is this constraint necessary? // TODO is this constraint necessary?
component.error(node.expression, compiler_errors.invalid_binding_window(parts)); return component.error(node.expression, compiler_errors.invalid_binding_window(parts));
} }
if (!~valid_bindings.indexOf(node.name)) { if (!~valid_bindings.indexOf(node.name)) {
@ -48,9 +48,9 @@ export default class Window extends Node {
); );
if (match) { if (match) {
component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:window>', ` (did you mean '${match}'?)`)); return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:window>', ` (did you mean '${match}'?)`));
} else { } else {
component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:window>', ` — valid bindings are ${list(valid_bindings)}`)); return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:window>', ` — valid bindings are ${list(valid_bindings)}`));
} }
} }

@ -86,7 +86,7 @@ export default class Expression {
if (name[0] === '$') { if (name[0] === '$') {
const store_name = name.slice(1); const store_name = name.slice(1);
if (template_scope.names.has(store_name) || scope.has(store_name)) { if (template_scope.names.has(store_name) || scope.has(store_name)) {
component.error(node, compiler_errors.contextual_store); return component.error(node, compiler_errors.contextual_store);
} }
} }

@ -123,6 +123,7 @@ export interface CompileOptions {
name?: string; name?: string;
filename?: string; filename?: string;
generate?: 'dom' | 'ssr' | false; generate?: 'dom' | 'ssr' | false;
errorMode?: 'throw' | 'warn';
varsReport?: 'full' | 'strict' | false; varsReport?: 'full' | 'strict' | false;
sourcemap?: object | string; sourcemap?: object | string;

@ -0,0 +1,6 @@
<script>
const dummy = 'foo';
</script>
<input bind:value={dummy}>
<input bind:value={undeclared}>

@ -0,0 +1,47 @@
[
{
"code": "invalid-binding",
"message": "Cannot bind to a variable which is not writable",
"pos": 61,
"start": {
"line": 5,
"column": 19,
"character": 61
},
"end": {
"line": 5,
"column": 24,
"character": 66
}
},
{
"code": "missing-declaration",
"message": "'undeclared' is not defined",
"pos": 88,
"start": {
"character": 88,
"column": 19,
"line": 6
},
"end": {
"character": 98,
"column": 29,
"line": 6
}
},
{
"code": "binding-undeclared",
"message": "undeclared is not declared",
"pos": 88,
"end": {
"character": 98,
"column": 29,
"line": 6
},
"start": {
"character": 88,
"column": 19,
"line": 6
}
}
]
Loading…
Cancel
Save