Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 17, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions site/content/docs/04-compile-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The following options can be passed to the compiler. None are required:
| `name` | string | `"Component"`
| `format` | `"esm"` or `"cjs"` | `"esm"`
| `generate` | `"dom"` or `"ssr"` | `"dom"`
| `errorMode` | `"throw"` or `"warn"` | `"throw"`
| `dev` | boolean | `false`
| `immutable` | boolean | `false`
| `hydratable` | boolean | `false`
Expand All @@ -66,6 +67,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`.
| `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.
| `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.
| `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.
| `hydratable` | `false` | If `true` when generating DOM code, enables the `hydrate: true` runtime option, which allows a component to upgrade existing DOM rather than creating new DOM from scratch. When generating SSR code, this adds markers to `<head>` elements so that hydration knows which to replace.
Expand Down
60 changes: 32 additions & 28 deletions src/compiler/compile/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,14 +412,18 @@ export default class Component {
message: string;
}
) {
error(e.message, {
name: 'ValidationError',
code: e.code,
source: this.source,
start: pos.start,
end: pos.end,
filename: this.compile_options.filename
});
if (this.compile_options.errorMode === 'warn') {
this.warn(pos, e);
} else {
error(e.message, {
name: 'ValidationError',
code: e.code,
source: this.source,
start: pos.start,
end: pos.end,
filename: this.compile_options.filename
});
}
}

warn(
Expand Down Expand Up @@ -460,15 +464,15 @@ export default class Component {

extract_exports(node) {
if (node.type === 'ExportDefaultDeclaration') {
this.error(node, {
return this.error(node, {
code: 'default-export',
message: 'A component cannot have a default export'
});
}

if (node.type === 'ExportNamedDeclaration') {
if (node.source) {
this.error(node, {
return this.error(node, {
code: 'not-implemented',
message: 'A component currently cannot have an export ... from'
});
Expand Down Expand Up @@ -550,7 +554,7 @@ export default class Component {

scope.declarations.forEach((node, name) => {
if (name[0] === '$') {
this.error(node as any, {
return this.error(node as any, {
code: 'illegal-declaration',
message: 'The $ prefix is reserved, and cannot be used for variable and import names'
});
Expand All @@ -568,7 +572,7 @@ export default class Component {

globals.forEach((node, name) => {
if (name[0] === '$') {
this.error(node as any, {
return this.error(node as any, {
code: 'illegal-subscription',
message: 'Cannot reference store value inside <script context="module">'
});
Expand Down Expand Up @@ -629,7 +633,7 @@ export default class Component {

instance_scope.declarations.forEach((node, name) => {
if (name[0] === '$') {
this.error(node as any, {
return this.error(node as any, {
code: 'illegal-declaration',
message: 'The $ prefix is reserved, and cannot be used for variable and import names'
});
Expand Down Expand Up @@ -666,7 +670,7 @@ export default class Component {
});
} else if (name[0] === '$') {
if (name === '$' || name[1] === '$') {
this.error(node as any, {
return this.error(node as any, {
code: 'illegal-global',
message: `${name} is an illegal variable name`
});
Expand Down Expand Up @@ -869,7 +873,7 @@ export default class Component {

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'))) {
this.error(node as any, {
return this.error(node as any, {
code: 'contextual-store',
message: 'Stores must be declared at the top level of the component (this may change in a future version of Svelte)'
});
Expand Down Expand Up @@ -937,7 +941,7 @@ export default class Component {

if (variable.export_name) {
// TODO is this still true post-#3539?
component.error(declarator as any, {
return component.error(declarator as any, {
code: 'destructured-prop',
message: 'Cannot declare props in destructured declaration'
});
Expand Down Expand Up @@ -1298,7 +1302,7 @@ export default class Component {
if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]);
const declaration = declarationList[0];
this.error(declaration.node, {
return this.error(declaration.node, {
code: 'cyclical-reactive-declaration',
message: `Cyclical dependency detected: ${cycle.join(' → ')}`
});
Expand All @@ -1324,7 +1328,7 @@ export default class Component {
warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') {
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {
this.error(node, {
return this.error(node, {
code: 'illegal-global',
message: `${name} is an illegal variable name`
});
Expand Down Expand Up @@ -1384,13 +1388,13 @@ function process_component_options(component: Component, nodes) {
if (!chunk) return true;

if (value.length > 1) {
component.error(attribute, { code, message });
return component.error(attribute, { code, message });
}

if (chunk.type === 'Text') return chunk.data;

if (chunk.expression.type !== 'Literal') {
component.error(attribute, { code, message });
return component.error(attribute, { code, message });
}

return chunk.expression.value;
Expand All @@ -1408,11 +1412,11 @@ function process_component_options(component: Component, nodes) {
const tag = get_value(attribute, code, message);

if (typeof tag !== 'string' && tag !== null) {
component.error(attribute, { code, message });
return component.error(attribute, { code, message });
}

if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
component.error(attribute, {
return component.error(attribute, {
code: 'invalid-tag-property',
message: "tag name must be two or more words joined by the '-' character"
});
Expand All @@ -1435,18 +1439,18 @@ function process_component_options(component: Component, nodes) {
const ns = get_value(attribute, code, message);

if (typeof ns !== 'string') {
component.error(attribute, { code, message });
return component.error(attribute, { code, message });
}

if (valid_namespaces.indexOf(ns) === -1) {
const match = fuzzymatch(ns, valid_namespaces);
if (match) {
component.error(attribute, {
return component.error(attribute, {
code: 'invalid-namespace-property',
message: `Invalid namespace '${ns}' (did you mean '${match}'?)`
});
} else {
component.error(attribute, {
return component.error(attribute, {
code: 'invalid-namespace-property',
message: `Invalid namespace '${ns}'`
});
Expand All @@ -1465,21 +1469,21 @@ function process_component_options(component: Component, nodes) {
const value = get_value(attribute, code, message);

if (typeof value !== 'boolean') {
component.error(attribute, { code, message });
return component.error(attribute, { code, message });
}

component_options[name] = value;
break;
}

default:
component.error(attribute, {
return component.error(attribute, {
code: 'invalid-options-attribute',
message: '<svelte:options> unknown attribute'
});
}
} else {
component.error(attribute, {
return component.error(attribute, {
code: 'invalid-options-attribute',
message: "<svelte:options> can only have static 'tag', 'namespace', 'accessors', 'immutable' and 'preserveWhitespace' attributes"
});
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/compile/css/Selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export default class Selector {
while (i-- > 1) {
const selector = block.selectors[i];
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
component.error(selector, {
return component.error(selector, {
code: 'css-invalid-global',
message: ':global(...) must be the first element in a compound selector'
});
Expand All @@ -141,7 +141,7 @@ export default class Selector {

for (let i = start; i < end; i += 1) {
if (this.blocks[i].global) {
component.error(this.blocks[i].selectors[0], {
return component.error(this.blocks[i].selectors[0], {
code: 'css-invalid-global',
message: ':global(...) can be at the start or end of a selector sequence, but not in the middle'
});
Expand Down
1 change: 1 addition & 0 deletions src/compiler/compile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const valid_options = [
'filename',
'sourcemap',
'generate',
'errorMode',
'outputFilename',
'cssOutputFilename',
'sveltePath',
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/compile/nodes/Animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default class Animation extends Node {
code: 'duplicate-animation',
message: "An element can only have one 'animate' directive"
});
return;
}

const block = parent.parent;
Expand All @@ -33,6 +34,7 @@ export default class Animation extends Node {
code: 'invalid-animation',
message: 'An element that uses the animate directive must be the immediate child of a keyed each block'
});
return;
}

(block as EachBlock).has_animation = true;
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/compile/nodes/Binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default class Binding extends Node {
code: 'invalid-directive-value',
message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)'
});
return;
}

this.name = info.name;
Expand All @@ -55,12 +56,14 @@ export default class Binding extends Node {
code: 'invalid-binding',
message: 'Cannot bind to a variable declared with the let: directive'
});
return;
} else if (scope.names.has(name)) {
if (scope.is_await(name)) {
component.error(this, {
code: 'invalid-binding',
message: 'Cannot bind to a variable declared with {#await ... then} or {:catch} blocks'
});
return;
}

scope.dependencies_for_name.get(name).forEach(name => {
Expand All @@ -77,6 +80,7 @@ export default class Binding extends Node {
code: 'binding-undeclared',
message: `${name} is not declared`
});
return;
}

variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
Expand All @@ -86,6 +90,7 @@ export default class Binding extends Node {
code: 'invalid-binding',
message: 'Cannot bind to a variable which is not writable'
});
return;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/compiler/compile/nodes/EachBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export default class EachBlock extends AbstractBlock {
code: 'invalid-animation',
message: 'An element that uses the animate directive must be the sole child of a keyed each block'
});
return;
}
}

Expand Down
Loading