Skip to content

Commit 0cf5511

Browse files
authored
[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: sveltejs/svelte-preprocess#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
1 parent 931738e commit 0cf5511

21 files changed

+150
-73
lines changed

site/content/docs/04-compile-time.md

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ The following options can be passed to the compiler. None are required:
4545
| `name` | string | `"Component"`
4646
| `format` | `"esm"` or `"cjs"` | `"esm"`
4747
| `generate` | `"dom"` or `"ssr"` or `false` | `"dom"`
48+
| `errorMode` | `"throw"` or `"warn"` | `"throw"`
4849
| `varsReport` | `"strict"` or `"full"` or `false` | `"strict"`
4950
| `dev` | boolean | `false`
5051
| `immutable` | boolean | `false`
@@ -67,6 +68,7 @@ The following options can be passed to the compiler. None are required:
6768
| `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`.
6869
| `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.
6970
| `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.
71+
| `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.
7072
| `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.
7173
| `dev` | `false` | If `true`, causes extra code to be added to components that will perform runtime checks and provide debugging information during development.
7274
| `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.

src/compiler/compile/Component.ts

+31-27
Original file line numberDiff line numberDiff line change
@@ -435,14 +435,18 @@ export default class Component {
435435
message: string;
436436
}
437437
) {
438-
error(e.message, {
439-
name: 'ValidationError',
440-
code: e.code,
441-
source: this.source,
442-
start: pos.start,
443-
end: pos.end,
444-
filename: this.compile_options.filename
445-
});
438+
if (this.compile_options.errorMode === 'warn') {
439+
this.warn(pos, e);
440+
} else {
441+
error(e.message, {
442+
name: 'ValidationError',
443+
code: e.code,
444+
source: this.source,
445+
start: pos.start,
446+
end: pos.end,
447+
filename: this.compile_options.filename
448+
});
449+
}
446450
}
447451

448452
warn(
@@ -491,12 +495,12 @@ export default class Component {
491495

492496
private _extract_exports(node) {
493497
if (node.type === 'ExportDefaultDeclaration') {
494-
this.error(node, compiler_errors.default_export);
498+
return this.error(node, compiler_errors.default_export);
495499
}
496500

497501
if (node.type === 'ExportNamedDeclaration') {
498502
if (node.source) {
499-
this.error(node, compiler_errors.not_implemented);
503+
return this.error(node, compiler_errors.not_implemented);
500504
}
501505
if (node.declaration) {
502506
if (node.declaration.type === 'VariableDeclaration') {
@@ -566,7 +570,7 @@ export default class Component {
566570

567571
scope.declarations.forEach((node, name) => {
568572
if (name[0] === '$') {
569-
this.error(node as any, compiler_errors.illegal_declaration);
573+
return this.error(node as any, compiler_errors.illegal_declaration);
570574
}
571575

572576
const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let');
@@ -581,7 +585,7 @@ export default class Component {
581585

582586
globals.forEach((node, name) => {
583587
if (name[0] === '$') {
584-
this.error(node as any, compiler_errors.illegal_subscription);
588+
return this.error(node as any, compiler_errors.illegal_subscription);
585589
} else {
586590
this.add_var({
587591
name,
@@ -639,7 +643,7 @@ export default class Component {
639643

640644
instance_scope.declarations.forEach((node, name) => {
641645
if (name[0] === '$') {
642-
this.error(node as any, compiler_errors.illegal_declaration);
646+
return this.error(node as any, compiler_errors.illegal_declaration);
643647
}
644648

645649
const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let');
@@ -673,7 +677,7 @@ export default class Component {
673677
});
674678
} else if (name[0] === '$') {
675679
if (name === '$' || name[1] === '$') {
676-
this.error(node as any, compiler_errors.illegal_global(name));
680+
return this.error(node as any, compiler_errors.illegal_global(name));
677681
}
678682

679683
this.add_var({
@@ -870,7 +874,7 @@ export default class Component {
870874

871875
if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) {
872876
if (!((/Function/.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) {
873-
this.error(node as any, compiler_errors.contextual_store);
877+
return this.error(node as any, compiler_errors.contextual_store);
874878
}
875879
}
876880
}
@@ -935,7 +939,7 @@ export default class Component {
935939

936940
if (variable.export_name) {
937941
// TODO is this still true post-#3539?
938-
component.error(declarator as any, compiler_errors.destructured_prop);
942+
return component.error(declarator as any, compiler_errors.destructured_prop);
939943
}
940944

941945
if (variable.subscribable) {
@@ -1300,7 +1304,7 @@ export default class Component {
13001304
if (cycle && cycle.length) {
13011305
const declarationList = lookup.get(cycle[0]);
13021306
const declaration = declarationList[0];
1303-
this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle));
1307+
return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle));
13041308
}
13051309

13061310
const add_declaration = declaration => {
@@ -1323,7 +1327,7 @@ export default class Component {
13231327
warn_if_undefined(name: string, node, template_scope: TemplateScope) {
13241328
if (name[0] === '$') {
13251329
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {
1326-
this.error(node, compiler_errors.illegal_global(name));
1330+
return this.error(node, compiler_errors.illegal_global(name));
13271331
}
13281332

13291333
this.has_reactive_assignments = true; // TODO does this belong here?
@@ -1372,13 +1376,13 @@ function process_component_options(component: Component, nodes) {
13721376
if (!chunk) return true;
13731377

13741378
if (value.length > 1) {
1375-
component.error(attribute, { code, message });
1379+
return component.error(attribute, { code, message });
13761380
}
13771381

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

13801384
if (chunk.expression.type !== 'Literal') {
1381-
component.error(attribute, { code, message });
1385+
return component.error(attribute, { code, message });
13821386
}
13831387

13841388
return chunk.expression.value;
@@ -1394,11 +1398,11 @@ function process_component_options(component: Component, nodes) {
13941398
const tag = get_value(attribute, compiler_errors.invalid_tag_attribute);
13951399

13961400
if (typeof tag !== 'string' && tag !== null) {
1397-
component.error(attribute, compiler_errors.invalid_tag_attribute);
1401+
return component.error(attribute, compiler_errors.invalid_tag_attribute);
13981402
}
13991403

14001404
if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
1401-
component.error(attribute, compiler_errors.invalid_tag_property);
1405+
return component.error(attribute, compiler_errors.invalid_tag_property);
14021406
}
14031407

14041408
if (tag && !component.compile_options.customElement) {
@@ -1413,12 +1417,12 @@ function process_component_options(component: Component, nodes) {
14131417
const ns = get_value(attribute, compiler_errors.invalid_namespace_attribute);
14141418

14151419
if (typeof ns !== 'string') {
1416-
component.error(attribute, compiler_errors.invalid_namespace_attribute);
1420+
return component.error(attribute, compiler_errors.invalid_namespace_attribute);
14171421
}
14181422

14191423
if (valid_namespaces.indexOf(ns) === -1) {
14201424
const match = fuzzymatch(ns, valid_namespaces);
1421-
component.error(attribute, compiler_errors.invalid_namespace_property(ns, match));
1425+
return component.error(attribute, compiler_errors.invalid_namespace_property(ns, match));
14221426
}
14231427

14241428
component_options.namespace = ns;
@@ -1431,18 +1435,18 @@ function process_component_options(component: Component, nodes) {
14311435
const value = get_value(attribute, compiler_errors.invalid_attribute_value(name));
14321436

14331437
if (typeof value !== 'boolean') {
1434-
component.error(attribute, compiler_errors.invalid_attribute_value(name));
1438+
return component.error(attribute, compiler_errors.invalid_attribute_value(name));
14351439
}
14361440

14371441
component_options[name] = value;
14381442
break;
14391443
}
14401444

14411445
default:
1442-
component.error(attribute, compiler_errors.invalid_options_attribute_unknown);
1446+
return component.error(attribute, compiler_errors.invalid_options_attribute_unknown);
14431447
}
14441448
} else {
1445-
component.error(attribute, compiler_errors.invalid_options_attribute);
1449+
return component.error(attribute, compiler_errors.invalid_options_attribute);
14461450
}
14471451
});
14481452
}

src/compiler/compile/css/Selector.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export default class Selector {
138138

139139
for (let i = start; i < end; i += 1) {
140140
if (this.blocks[i].global) {
141-
component.error(this.blocks[i].selectors[0], compiler_errors.css_invalid_global);
141+
return component.error(this.blocks[i].selectors[0], compiler_errors.css_invalid_global);
142142
}
143143
}
144144

src/compiler/compile/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const valid_options = [
1414
'filename',
1515
'sourcemap',
1616
'generate',
17+
'errorMode',
1718
'varsReport',
1819
'outputFilename',
1920
'cssOutputFilename',

src/compiler/compile/nodes/Animation.ts

+2
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ export default class Animation extends Node {
2222

2323
if (parent.animation) {
2424
component.error(this, compiler_errors.duplicate_animation);
25+
return;
2526
}
2627

2728
const block = parent.parent;
2829
if (!block || block.type !== 'EachBlock' || !block.key) {
2930
// TODO can we relax the 'immediate child' rule?
3031
component.error(this, compiler_errors.invalid_animation_immediate);
32+
return;
3133
}
3234

3335
(block as EachBlock).has_animation = true;

src/compiler/compile/nodes/Binding.ts

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default class Binding extends Node {
3737

3838
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {
3939
component.error(info, compiler_errors.invalid_directive_value);
40+
return;
4041
}
4142

4243
this.name = info.name;
@@ -50,9 +51,11 @@ export default class Binding extends Node {
5051
// make sure we track this as a mutable ref
5152
if (scope.is_let(name)) {
5253
component.error(this, compiler_errors.invalid_binding_let);
54+
return;
5355
} else if (scope.names.has(name)) {
5456
if (scope.is_await(name)) {
5557
component.error(this, compiler_errors.invalid_binding_await);
58+
return;
5659
}
5760

5861
scope.dependencies_for_name.get(name).forEach(name => {
@@ -66,12 +69,14 @@ export default class Binding extends Node {
6669

6770
if (!variable || variable.global) {
6871
component.error(this.expression.node as any, compiler_errors.binding_undeclared(name));
72+
return;
6973
}
7074

7175
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
7276

7377
if (info.expression.type === 'Identifier' && !variable.writable) {
7478
component.error(this.expression.node as any, compiler_errors.invalid_binding_writibale);
79+
return;
7580
}
7681
}
7782

src/compiler/compile/nodes/EachBlock.ts

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export default class EachBlock extends AbstractBlock {
6363
if (this.children.length !== 1) {
6464
const child = this.children.find(child => !!(child as Element).animation);
6565
component.error((child as Element).animation, compiler_errors.invalid_animation_sole);
66+
return;
6667
}
6768
}
6869

0 commit comments

Comments
 (0)