diff --git a/src/css/Selector.ts b/src/css/Selector.ts
index 658f6dc52d..fba0386f1f 100644
--- a/src/css/Selector.ts
+++ b/src/css/Selector.ts
@@ -102,7 +102,7 @@ export default class Selector {
while (i-- > 1) {
const selector = block.selectors[i];
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
- validator.error(`:global(...) must be the first element in a compound selector`, selector.start);
+ validator.error(`:global(...) must be the first element in a compound selector`, selector);
}
}
});
@@ -120,7 +120,7 @@ export default class Selector {
for (let i = start; i < end; i += 1) {
if (this.blocks[i].global) {
- validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, this.blocks[i].selectors[0].start);
+ validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, this.blocks[i].selectors[0]);
}
}
}
diff --git a/src/index.ts b/src/index.ts
index be62f07763..cc3c0681e5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -146,4 +146,4 @@ export function create(source: string, _options: CompileOptions = {}) {
}
}
-export { parse, validate, version as VERSION };
+export { parse, validate, Stylesheet, version as VERSION };
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 82de8cd047..de187da71d 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -29,6 +29,7 @@ export interface Parsed {
export interface Warning {
loc?: { line: number; column: number; pos?: number };
+ end?: { line: number; column: number; };
pos?: number;
message: string;
filename?: string;
diff --git a/src/utils/CompileError.ts b/src/utils/CompileError.ts
index 16dabc3c13..c48edba4ab 100644
--- a/src/utils/CompileError.ts
+++ b/src/utils/CompileError.ts
@@ -4,24 +4,28 @@ import getCodeFrame from '../utils/getCodeFrame';
export default class CompileError extends Error {
frame: string;
loc: { line: number; column: number };
+ end: { line: number; column: number };
pos: number;
filename: string;
constructor(
message: string,
template: string,
- index: number,
- filename: string
+ startPos: number,
+ filename: string,
+ endPos: number = startPos
) {
super(message);
- const { line, column } = locate(template, index);
+ const start = locate(template, startPos);
+ const end = locate(template, endPos);
- this.loc = { line: line + 1, column };
- this.pos = index;
+ this.loc = { line: start.line + 1, column: start.column };
+ this.end = { line: end.line + 1, column: end.column };
+ this.pos = startPos;
this.filename = filename;
- this.frame = getCodeFrame(template, line, column);
+ this.frame = getCodeFrame(template, start.line, start.column);
}
public toString = () => {
diff --git a/src/validate/html/a11y.ts b/src/validate/html/a11y.ts
index 1e07c0b4d4..e9953d6083 100644
--- a/src/validate/html/a11y.ts
+++ b/src/validate/html/a11y.ts
@@ -33,7 +33,7 @@ export default function a11y(
if (name.startsWith('aria-')) {
if (invisibleElements.has(node.name)) {
// aria-unsupported-elements
- validator.warn(`A11y: <${node.name}> should not have aria-* attributes`, attribute.start);
+ validator.warn(`A11y: <${node.name}> should not have aria-* attributes`, attribute);
}
const type = name.slice(5);
@@ -42,7 +42,7 @@ export default function a11y(
let message = `A11y: Unknown aria attribute 'aria-${type}'`;
if (match) message += ` (did you mean '${match}'?)`;
- validator.warn(message, attribute.start);
+ validator.warn(message, attribute);
}
}
@@ -50,7 +50,7 @@ export default function a11y(
if (name === 'role') {
if (invisibleElements.has(node.name)) {
// aria-unsupported-elements
- validator.warn(`A11y: <${node.name}> should not have role attribute`, attribute.start);
+ validator.warn(`A11y: <${node.name}> should not have role attribute`, attribute);
}
const value = getStaticAttributeValue(node, 'role');
@@ -59,30 +59,30 @@ export default function a11y(
let message = `A11y: Unknown role '${value}'`;
if (match) message += ` (did you mean '${match}'?)`;
- validator.warn(message, attribute.start);
+ validator.warn(message, attribute);
}
}
// no-access-key
if (name === 'accesskey') {
- validator.warn(`A11y: Avoid using accesskey`, attribute.start);
+ validator.warn(`A11y: Avoid using accesskey`, attribute);
}
// no-autofocus
if (name === 'autofocus') {
- validator.warn(`A11y: Avoid using autofocus`, attribute.start);
+ validator.warn(`A11y: Avoid using autofocus`, attribute);
}
// scope
if (name === 'scope' && node.name !== 'th') {
- validator.warn(`A11y: The scope attribute should only be used with
elements`, attribute.start);
+ validator.warn(`A11y: The scope attribute should only be used with | elements`, attribute);
}
// tabindex-no-positive
if (name === 'tabindex') {
const value = getStaticAttributeValue(node, 'tabindex');
if (!isNaN(value) && +value > 0) {
- validator.warn(`A11y: avoid tabindex values above zero`, attribute.start);
+ validator.warn(`A11y: avoid tabindex values above zero`, attribute);
}
}
@@ -96,13 +96,13 @@ export default function a11y(
attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` :
attributes[0];
- validator.warn(`A11y: <${name}> element should have ${article} ${sequence} attribute`, node.start);
+ validator.warn(`A11y: <${name}> element should have ${article} ${sequence} attribute`, node);
}
}
function shouldHaveContent() {
if (node.children.length === 0) {
- validator.warn(`A11y: <${node.name}> element should have child content`, node.start);
+ validator.warn(`A11y: <${node.name}> element should have child content`, node);
}
}
@@ -110,7 +110,7 @@ export default function a11y(
const href = attributeMap.get(attribute);
const value = getStaticAttributeValue(node, attribute);
if (value === '' || value === '#') {
- validator.warn(`A11y: '${value}' is not a valid ${attribute} attribute`, href.start);
+ validator.warn(`A11y: '${value}' is not a valid ${attribute} attribute`, href);
}
}
@@ -122,7 +122,7 @@ export default function a11y(
// anchor-in-svg-is-valid
shouldHaveValidHref('xlink:href')
} else {
- validator.warn(`A11y: element should have an href attribute`, node.start);
+ validator.warn(`A11y: element should have an href attribute`, node);
}
// anchor-has-content
@@ -141,7 +141,7 @@ export default function a11y(
shouldHaveContent();
if (attributeMap.has('aria-hidden')) {
- validator.warn(`A11y: <${node.name}> element should not be hidden`, attributeMap.get('aria-hidden').start);
+ validator.warn(`A11y: <${node.name}> element should not be hidden`, attributeMap.get('aria-hidden'));
}
}
@@ -157,14 +157,14 @@ export default function a11y(
// no-distracting-elements
if (node.name === 'marquee' || node.name === 'blink') {
- validator.warn(`A11y: Avoid <${node.name}> elements`, node.start);
+ validator.warn(`A11y: Avoid <${node.name}> elements`, node);
}
if (node.name === 'figcaption') {
const parent = elementStack[elementStack.length - 1];
if (parent) {
if (parent.name !== 'figure') {
- validator.warn(`A11y: must be an immediate child of `, node.start);
+ validator.warn(`A11y: must be an immediate child of `, node);
} else {
const children = parent.children.filter(node => {
if (node.type === 'Comment') return false;
@@ -175,7 +175,7 @@ export default function a11y(
const index = children.indexOf(node);
if (index !== 0 && index !== children.length - 1) {
- validator.warn(`A11y: must be first or last child of `, node.start);
+ validator.warn(`A11y: must be first or last child of `, node);
}
}
}
diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts
index 680b8b729b..36377f4065 100644
--- a/src/validate/html/index.ts
+++ b/src/validate/html/index.ts
@@ -52,7 +52,7 @@ export default function validateHtml(validator: Validator, html: Node) {
else if (node.type === 'EachBlock') {
if (validator.helpers.has(node.context)) {
- let c = node.expression.end;
+ let c: number = node.expression.end;
// find start of context
while (/\s/.test(validator.source[c])) c += 1;
@@ -61,13 +61,13 @@ export default function validateHtml(validator: Validator, html: Node) {
validator.warn(
`Context clashes with a helper. Rename one or the other to eliminate any ambiguity`,
- c
+ { start: c, end: c + node.context.length }
);
}
}
if (validator.options.dev && isEmptyBlock(node)) {
- validator.warn('Empty block', node.start);
+ validator.warn('Empty block', node);
}
if (node.children) {
@@ -103,7 +103,7 @@ export default function validateHtml(validator: Validator, html: Node) {
let message = `'refs.${ref}' does not exist`;
if (match) message += ` (did you mean 'refs.${match}'?)`;
- validator.error(message, callee.start);
+ validator.error(message, callee);
}
});
}
diff --git a/src/validate/html/validateElement.ts b/src/validate/html/validateElement.ts
index 550f8d2ae7..9172854c7a 100644
--- a/src/validate/html/validateElement.ts
+++ b/src/validate/html/validateElement.ts
@@ -20,13 +20,13 @@ export default function validateElement(
if (!isComponent && /^[A-Z]/.test(node.name[0])) {
// TODO upgrade to validator.error in v2
- validator.warn(`${node.name} component is not defined`, node.start);
+ validator.warn(`${node.name} component is not defined`, node);
}
if (elementStack.length === 0 && validator.namespace !== namespaces.svg && svg.test(node.name)) {
validator.warn(
`<${node.name}> is an SVG element – did you forget to add { namespace: 'svg' } ?`,
- node.start
+ node
);
}
@@ -34,12 +34,12 @@ export default function validateElement(
const nameAttribute = node.attributes.find((attribute: Node) => attribute.name === 'name');
if (nameAttribute) {
if (nameAttribute.value.length !== 1 || nameAttribute.value[0].type !== 'Text') {
- validator.error(` name cannot be dynamic`, nameAttribute.start);
+ validator.error(` name cannot be dynamic`, nameAttribute);
}
const slotName = nameAttribute.value[0].data;
if (slotName === 'default') {
- validator.error(`default is a reserved word — it cannot be used as a slot name`, nameAttribute.start);
+ validator.error(`default is a reserved word — it cannot be used as a slot name`, nameAttribute);
}
// TODO should duplicate slots be disallowed? Feels like it's more likely to be a
@@ -63,7 +63,7 @@ export default function validateElement(
if (node.attributes.length > 0) {
validator.error(
` cannot have attributes`,
- node.attributes[0].start
+ node.attributes[0]
);
}
@@ -71,7 +71,7 @@ export default function validateElement(
if (child.type !== 'Text' && child.type !== 'MustacheTag') {
validator.error(
` can only contain text and {{tags}}`,
- child.start
+ child
);
}
});
@@ -98,7 +98,7 @@ export default function validateElement(
) {
validator.error(
`'value' is not a valid binding on <${node.name}> elements`,
- attribute.start
+ attribute
);
}
@@ -107,21 +107,21 @@ export default function validateElement(
if (node.name !== 'input') {
validator.error(
`'${name}' is not a valid binding on <${node.name}> elements`,
- attribute.start
+ attribute
);
}
if (checkTypeAttribute(validator, node) !== 'checkbox') {
validator.error(
`'${name}' binding can only be used with `,
- attribute.start
+ attribute
);
}
} else if (name === 'group') {
if (node.name !== 'input') {
validator.error(
`'group' is not a valid binding on <${node.name}> elements`,
- attribute.start
+ attribute
);
}
@@ -130,7 +130,7 @@ export default function validateElement(
if (type !== 'checkbox' && type !== 'radio') {
validator.error(
`'checked' binding can only be used with or `,
- attribute.start
+ attribute
);
}
} else if (
@@ -145,13 +145,13 @@ export default function validateElement(
if (node.name !== 'audio' && node.name !== 'video') {
validator.error(
`'${name}' binding can only be used with |