Skip to content

Commit 2265798

Browse files
committed
Add file, row and column for ui/builder errors
1 parent 8a70369 commit 2265798

8 files changed

Lines changed: 349 additions & 122 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Page shownModally="onShownModally">
2+
<StackLayout>
3+
<!--
4+
At first I was going to put "MenuItem",
5+
as per https://github.com/NativeScript/NativeScript/issues/501,
6+
but then again we may re-introduce "MenuItem" in future and blow this test up.
7+
So here is something unique that has better chance not to appear.
8+
This comment also offsets error's row and column numbers so if you edit,
9+
please do so beyond the unicorn.
10+
-->
11+
<Unicorn backgroundColor="pink" />
12+
<Label text="Modal Page" />
13+
</StackLayout>
14+
</Page>

apps/tests/xml-declaration/xml-declaration-tests.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,4 +835,24 @@ export function test_parse_template_property() {
835835
var button = <Button>templateView.getChildAt(0);
836836
TKUnit.assert(button, "Expected the TemplateView's template to create a button child.");
837837
TKUnit.assertEqual(button.text, "Click!", "Expected child Button to have text 'Click!'");
838-
}
838+
}
839+
840+
export function test_ParserError() {
841+
var basePath = "xml-declaration/";
842+
var expectedErrorStart =
843+
"Building UI from XML. @file:///app/" + basePath + "errors/non-existing-element.xml:11:5\n" +
844+
" ↳Module 'ui/unicorn' not found for element 'Unicorn'.\n";
845+
if (global.android) {
846+
expectedErrorStart += " ↳Module \"ui/unicorn\" not found";
847+
} else {
848+
expectedErrorStart += " ↳Failed to find module 'ui/unicorn'";
849+
}
850+
851+
var message;
852+
try {
853+
builder.load(__dirname + "/errors/non-existing-element.xml");
854+
} catch(e) {
855+
message = e.message;
856+
}
857+
TKUnit.assertEqual(message.substr(0, expectedErrorStart.length), expectedErrorStart, "Expected load to throw, and the message to start with specific string");
858+
}

tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,8 @@
319319
"apps/tests/xml-declaration/mainPage.ts",
320320
"apps/tests/xml-declaration/mymodule/MyControl.ts",
321321
"apps/tests/xml-declaration/mymodulewithxml/MyControl.ts",
322-
"apps/tests/xml-declaration/xml-declaration-tests.ts",
323322
"apps/tests/xml-declaration/template-builder-tests/template-view.ts",
323+
"apps/tests/xml-declaration/xml-declaration-tests.ts",
324324
"apps/tests/xml-parser-tests/xml-parser-tests.ts",
325325
"apps/transforms/app.ts",
326326
"apps/transforms/main-page.ts",
@@ -658,6 +658,8 @@
658658
"ui/web-view/web-view.android.ts",
659659
"ui/web-view/web-view.d.ts",
660660
"ui/web-view/web-view.ios.ts",
661+
"utils/debug.d.ts",
662+
"utils/debug.ts",
661663
"utils/module-merge.ts",
662664
"utils/number-utils.ts",
663665
"utils/types.d.ts",

ui/builder/builder.ts

Lines changed: 123 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import definition = require("ui/builder");
99
import page = require("ui/page");
1010
import fileResolverModule = require("file-system/file-name-resolver");
1111
import trace = require("trace");
12+
import debug = require("utils/debug");
1213

1314
var KNOWNCOLLECTIONS = "knownCollections";
1415

@@ -41,7 +42,7 @@ export function parse(value: string | view.Template, context: any): view.View {
4142
}
4243
}
4344

44-
function parseInternal(value: string, context: any): componentBuilder.ComponentModule {
45+
function parseInternal(value: string, context: any, uri?: string): componentBuilder.ComponentModule {
4546
var currentPage: page.Page;
4647
var rootComponentModule: componentBuilder.ComponentModule;
4748
// Temporary collection used for parent scope.
@@ -51,134 +52,148 @@ function parseInternal(value: string, context: any): componentBuilder.ComponentM
5152
var templateBuilder: templateBuilderDef.TemplateBuilder;
5253

5354
var currentPlatformContext: string;
55+
56+
var wrapSource: (e: Error, p: xml.Position) => Error;
57+
if (debug.debug && uri) {
58+
wrapSource = (e: Error, p: xml.Position) => {
59+
var source = new debug.Source(uri, p.line, p.column);
60+
e = new debug.SourceError(e, source, "Building UI from XML.");
61+
return e;
62+
}
63+
} else {
64+
wrapSource = e => e; // no-op identity
65+
}
5466

5567
// Parse the XML.
5668
var xmlParser = new xml.XmlParser((args: xml.ParserEvent) => {
57-
58-
if (args.eventType === xml.ParserEventType.StartElement) {
59-
if (isPlatform(args.elementName)) {
60-
61-
if (currentPlatformContext) {
62-
throw new Error("Already in '" + currentPlatformContext + "' platform context and cannot switch to '" + args.elementName + "' platform! Platform tags cannot be nested.");
69+
try {
70+
if (args.eventType === xml.ParserEventType.StartElement) {
71+
if (isPlatform(args.elementName)) {
72+
73+
if (currentPlatformContext) {
74+
throw new Error("Already in '" + currentPlatformContext + "' platform context and cannot switch to '" + args.elementName + "' platform! Platform tags cannot be nested.");
75+
}
76+
77+
currentPlatformContext = args.elementName;
78+
return;
6379
}
64-
65-
currentPlatformContext = args.elementName;
66-
return;
67-
}
68-
}
69-
70-
if (args.eventType === xml.ParserEventType.EndElement) {
71-
if (isPlatform(args.elementName)) {
72-
currentPlatformContext = undefined;
73-
return;
7480
}
75-
}
76-
77-
if (currentPlatformContext && !isCurentPlatform(currentPlatformContext)) {
78-
return;
79-
}
80-
81-
if (templateBuilder) {
82-
var finished = templateBuilder.handleElement(args);
83-
if (finished) {
84-
// Clean-up and continnue
85-
templateBuilder = undefined;
81+
82+
if (args.eventType === xml.ParserEventType.EndElement) {
83+
if (isPlatform(args.elementName)) {
84+
currentPlatformContext = undefined;
85+
return;
86+
}
8687
}
87-
else {
88-
// Skip processing untill the template builder finishes his job.
88+
89+
if (currentPlatformContext && !isCurentPlatform(currentPlatformContext)) {
8990
return;
9091
}
91-
}
92-
93-
// Get the current parent.
94-
var parent = parents[parents.length - 1];
95-
var complexProperty = complexProperties[complexProperties.length - 1];
96-
97-
// Create component instance from every element declaration.
98-
if (args.eventType === xml.ParserEventType.StartElement) {
99-
if (isComplexProperty(args.elementName)) {
100-
101-
var name = getComplexProperty(args.elementName);
102-
103-
complexProperties.push({
104-
parent: parent,
105-
name: name,
106-
items: [],
107-
});
108-
109-
if (templateBuilderDef.isKnownTemplate(name, parent.exports)) {
110-
templateBuilder = new templateBuilderDef.TemplateBuilder({
111-
context: parent ? getExports(parent.component) : null, // Passing 'context' won't work if you set "codeFile" on the page
92+
93+
if (templateBuilder) {
94+
var finished = templateBuilder.handleElement(args);
95+
if (finished) {
96+
// Clean-up and continnue
97+
templateBuilder = undefined;
98+
}
99+
else {
100+
// Skip processing untill the template builder finishes his job.
101+
return;
102+
}
103+
}
104+
105+
// Get the current parent.
106+
var parent = parents[parents.length - 1];
107+
var complexProperty = complexProperties[complexProperties.length - 1];
108+
109+
// Create component instance from every element declaration.
110+
if (args.eventType === xml.ParserEventType.StartElement) {
111+
if (isComplexProperty(args.elementName)) {
112+
113+
var name = getComplexProperty(args.elementName);
114+
115+
complexProperties.push({
112116
parent: parent,
113117
name: name,
114-
elementName: args.elementName,
115-
templateItems: []
118+
items: [],
116119
});
117-
}
118-
119-
} else {
120-
121-
var componentModule: componentBuilder.ComponentModule;
122-
123-
if (args.prefix && args.namespace) {
124-
// Custom components
125-
componentModule = loadCustomComponent(args.namespace, args.elementName, args.attributes, context, currentPage);
120+
121+
if (templateBuilderDef.isKnownTemplate(name, parent.exports)) {
122+
templateBuilder = new templateBuilderDef.TemplateBuilder({
123+
context: parent ? getExports(parent.component) : null, // Passing 'context' won't work if you set "codeFile" on the page
124+
parent: parent,
125+
name: name,
126+
elementName: args.elementName,
127+
templateItems: []
128+
});
129+
}
130+
126131
} else {
127-
// Default components
128-
componentModule = componentBuilder.getComponentModule(args.elementName, args.namespace, args.attributes, context);
129-
}
130-
131-
if (componentModule) {
132-
if (parent) {
133-
if (componentModule.component instanceof view.View) {
134-
if (complexProperty) {
135-
// Add to complex property to component.
136-
addToComplexProperty(parent, complexProperty, componentModule)
132+
133+
var componentModule: componentBuilder.ComponentModule;
134+
135+
if (args.prefix && args.namespace) {
136+
// Custom components
137+
componentModule = loadCustomComponent(args.namespace, args.elementName, args.attributes, context, currentPage);
138+
} else {
139+
// Default components
140+
componentModule = componentBuilder.getComponentModule(args.elementName, args.namespace, args.attributes, context);
141+
}
142+
143+
if (componentModule) {
144+
if (parent) {
145+
if (componentModule.component instanceof view.View) {
146+
if (complexProperty) {
147+
// Add to complex property to component.
148+
addToComplexProperty(parent, complexProperty, componentModule)
149+
} else if ((<any>parent.component)._addChildFromBuilder) {
150+
// Add component to visual tree
151+
(<any>parent.component)._addChildFromBuilder(args.elementName, componentModule.component);
152+
}
153+
} else if (complexProperty) {
154+
// Add component to complex property of parent component.
155+
addToComplexProperty(parent, complexProperty, componentModule);
137156
} else if ((<any>parent.component)._addChildFromBuilder) {
138-
// Add component to visual tree
139157
(<any>parent.component)._addChildFromBuilder(args.elementName, componentModule.component);
140158
}
141-
} else if (complexProperty) {
142-
// Add component to complex property of parent component.
143-
addToComplexProperty(parent, complexProperty, componentModule);
144-
} else if ((<any>parent.component)._addChildFromBuilder) {
145-
(<any>parent.component)._addChildFromBuilder(args.elementName, componentModule.component);
146-
}
147-
} else if (parents.length === 0) {
148-
// Set root component.
149-
rootComponentModule = componentModule;
150-
151-
if (rootComponentModule && rootComponentModule.component instanceof page.Page) {
152-
currentPage = <page.Page>rootComponentModule.component;
159+
} else if (parents.length === 0) {
160+
// Set root component.
161+
rootComponentModule = componentModule;
162+
163+
if (rootComponentModule && rootComponentModule.component instanceof page.Page) {
164+
currentPage = <page.Page>rootComponentModule.component;
165+
}
153166
}
167+
168+
// Add the component instance to the parents scope collection.
169+
parents.push(componentModule);
154170
}
155-
156-
// Add the component instance to the parents scope collection.
157-
parents.push(componentModule);
158171
}
159-
}
160-
161-
} else if (args.eventType === xml.ParserEventType.EndElement) {
162-
if (isComplexProperty(args.elementName)) {
163-
if (complexProperty) {
164-
if (parent && (<any>parent.component)._addArrayFromBuilder) {
165-
// If parent is AddArrayFromBuilder call the interface method to populate the array property.
166-
(<any>parent.component)._addArrayFromBuilder(complexProperty.name, complexProperty.items);
167-
complexProperty.items = [];
172+
173+
} else if (args.eventType === xml.ParserEventType.EndElement) {
174+
if (isComplexProperty(args.elementName)) {
175+
if (complexProperty) {
176+
if (parent && (<any>parent.component)._addArrayFromBuilder) {
177+
// If parent is AddArrayFromBuilder call the interface method to populate the array property.
178+
(<any>parent.component)._addArrayFromBuilder(complexProperty.name, complexProperty.items);
179+
complexProperty.items = [];
180+
}
168181
}
182+
// Remove the last complexProperty from the complexProperties collection (move to the previous complexProperty scope).
183+
complexProperties.pop();
184+
185+
} else {
186+
// Remove the last parent from the parents collection (move to the previous parent scope).
187+
parents.pop();
169188
}
170-
// Remove the last complexProperty from the complexProperties collection (move to the previous complexProperty scope).
171-
complexProperties.pop();
172-
173-
} else {
174-
// Remove the last parent from the parents collection (move to the previous parent scope).
175-
parents.pop();
176189
}
190+
191+
} catch(e) {
192+
throw wrapSource(e, args.position);
177193
}
178-
179-
}, (e) => {
180-
throw new Error("XML parse error: " + e.message);
181-
}, true);
194+
}, (e, p) => {
195+
throw wrapSource(new Error("XML parse error: " + e.message), p);
196+
}, true);
182197

183198
if (types.isString(value)) {
184199
value = value.replace(/xmlns=("|')http:\/\/((www)|(schemas))\.nativescript\.org\/tns\.xsd\1/, "");
@@ -271,7 +286,7 @@ function loadInternal(fileName: string, context?: any): componentBuilder.Compone
271286
throw new Error("Error loading file " + fileName + " :" + error.message);
272287
}
273288
var text = file.readTextSync(onError);
274-
componentModule = parseInternal(text, context);
289+
componentModule = parseInternal(text, context, fileName);
275290
}
276291

277292
if (componentModule && componentModule.component) {

ui/builder/component-builder.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import fs = require("file-system");
55
import bindingBuilder = require("./binding-builder");
66
import platform = require("platform");
77
import pages = require("ui/page");
8+
import debug = require("utils/debug");
89

910
//the imports below are needed for special property registration
1011
import "ui/layouts/dock-layout";
@@ -60,7 +61,7 @@ export function getComponentModule(elementName: string, namespace: string, attri
6061
// Create instance of the component.
6162
instance = new instanceType();
6263
} catch (ex) {
63-
throw new Error("Cannot create module " + moduleId + ". " + ex + ". StackTrace: " + ex.stack);
64+
throw new debug.ScopeError(ex, "Module '" + moduleId + "' not found for element '" + (namespace ? namespace + ":" : "") + elementName + "'.");
6465
}
6566

6667
if (attributes) {

0 commit comments

Comments
 (0)