diff --git a/src/Error.ts b/src/Error.ts index d6c40c200..74079a368 100644 --- a/src/Error.ts +++ b/src/Error.ts @@ -11,11 +11,22 @@ export class BrsError extends Error { * and column, and the message associated with the error, e.g.: * * `lorem.brs(1,1-3): Expected '(' after sub name` - * ``` + * @see BrsError#format */ format() { - let location = this.location; + return BrsError.format(this.message, this.location); + } + /** + * Formats a location and message into a human-readable string including filename, starting + * and ending line and column, and the message associated with the error, e.g.: + * + * `lorem.brs(1,1-3): Expected '(' after sub name` + * + * @param message a string describing the error + * @param location where the error occurred + */ + static format(message: string, location: Location): string { let formattedLocation: string; if (location.start.line === location.end.line) { @@ -28,7 +39,7 @@ export class BrsError extends Error { formattedLocation = `${location.file}(${location.start.line},${location.start.column},${location.end.line},${location.end.line})`; } - return `${formattedLocation}: ${this.message}\n`; + return `${formattedLocation}: ${message}\n`; } } diff --git a/src/componentprocessor/index.ts b/src/componentprocessor/index.ts index beb4e754e..152ebaaa9 100644 --- a/src/componentprocessor/index.ts +++ b/src/componentprocessor/index.ts @@ -6,7 +6,7 @@ import pSettle = require("p-settle"); const readFile = promisify(fs.readFile); import * as fg from "fast-glob"; import { Environment } from "../interpreter/Environment"; -import { BrsComponentName } from "../brsTypes"; +import { BrsError } from "../Error"; interface FieldAttributes { id: string; @@ -89,11 +89,12 @@ export async function getComponentDefinitionMap(rootDir: string) { let defs = xmlFiles.map((file) => new ComponentDefinition(file)); let parsedPromises = defs.map(async (def) => def.parse()); - return processXmlTree(pSettle(parsedPromises)); + return processXmlTree(pSettle(parsedPromises), rootDir); } async function processXmlTree( - settledPromises: Promise[]> + settledPromises: Promise[]>, + rootDir: string ) { let nodeDefs = await settledPromises; let nodeDefMap = new Map(); @@ -151,13 +152,13 @@ async function processXmlTree( } }); - nodeDefMap.forEach((nodeDef) => { + for (let nodeDef of nodeDefMap.values()) { let xmlNode = nodeDef.xmlNode; if (xmlNode) { nodeDef.children = getChildren(xmlNode); - nodeDef.scripts = getScripts(xmlNode); + nodeDef.scripts = await getScripts(xmlNode, nodeDef.xmlPath, rootDir); } - }); + } return nodeDefMap; } @@ -241,19 +242,64 @@ function parseChildren(element: XmlElement, children: ComponentNode[]): void { }); } -function getScripts(node: XmlDocument): ComponentScript[] { +async function getScripts( + node: XmlDocument, + xmlPath: string, + rootDir: string +): Promise { let scripts = node.childrenNamed("script"); let componentScripts: ComponentScript[] = []; - // TODO: Verify if uri is valid - scripts.map((script) => { + for (let script of scripts) { + let absoluteUri: URL; + try { + absoluteUri = new URL(script.attr.uri, `pkg:/${path.posix.relative(rootDir, xmlPath)}`); + } catch (err) { + let file = await readFile(xmlPath, "utf-8"); + + let tag = file.substring(script.startTagPosition, script.position); + let tagLines = tag.split("\n"); + let leadingLines = file.substring(0, script.startTagPosition).split("\n"); + let start = { + line: leadingLines.length, + column: columnsInLastLine(leadingLines), + }; + + return Promise.reject({ + message: BrsError.format( + `Invalid path '${script.attr.uri}' found in