@html-validate/plugin-utils
Version:
Plugin utilities and helpers for writing plugins to HTML-Validate
183 lines (180 loc) • 5.16 kB
JavaScript
// src/position-from-offset.ts
function positionFromOffset(text, offset) {
let line = 1;
let prev = 0;
let pos = text.indexOf("\n");
while (pos !== -1) {
if (pos >= offset) {
return [line, offset - prev + 1];
}
line++;
prev = pos + 1;
pos = text.indexOf("\n", pos + 1);
}
return [line, offset - prev + 1];
}
// src/position-to-offset.ts
function positionToOffset(position, data) {
let line = position.line;
let column = position.column + 1;
for (let i = 0; i < data.length; i++) {
if (line > 1) {
if (data[i] === "\n") {
line--;
}
} else if (column > 1) {
column--;
} else {
return i;
}
}
throw new Error("Failed to compute location offset from position");
}
// src/template-extractor.ts
import * as espree from "espree";
import * as walk from "acorn-walk";
function joinTemplateLiteral(nodes) {
let offset = nodes[0].start + 1;
let output = "";
for (const node of nodes) {
output += " ".repeat(node.start + 1 - offset);
output += node.value.raw;
offset = node.end - 2;
}
return output;
}
function extractLiteral(node, filename, data) {
switch (node.type) {
/* ignored nodes */
case "FunctionExpression":
case "Identifier":
return null;
case "Literal":
if (typeof node.value !== "string") {
return null;
}
return {
data: node.value.toString(),
filename,
line: node.loc.start.line,
column: node.loc.start.column + 1,
offset: positionToOffset(node.loc.start, data) + 1
};
case "TemplateLiteral":
return {
data: joinTemplateLiteral(node.quasis),
filename,
line: node.loc.start.line,
column: node.loc.start.column + 1,
offset: positionToOffset(node.loc.start, data) + 1
};
case "TaggedTemplateExpression":
return {
data: joinTemplateLiteral(node.quasi.quasis),
filename,
line: node.quasi.loc.start.line,
column: node.quasi.loc.start.column + 1,
offset: positionToOffset(node.quasi.loc.start, data) + 1
};
case "ArrowFunctionExpression": {
const whitelist = ["Literal", "TemplateLiteral"];
if (whitelist.includes(node.body.type)) {
return extractLiteral(node.body, filename, data);
} else {
return null;
}
}
/* istanbul ignore next: this only provides a better error, all currently known nodes are tested */
default: {
const loc = node.loc.start;
const line = String(loc.line);
const column = String(loc.column);
const context = `${filename}:${line}:${column}`;
throw new Error(`Unhandled node type "${node.type}" at "${context}" in extractLiteral`);
}
}
}
function compareKey(node, key, filename) {
switch (node.type) {
case "Identifier":
return node.name === key;
case "Literal":
return node.value === key;
/* istanbul ignore next: this only provides a better error, all currently known nodes are tested */
default: {
const loc = node.loc.start;
const line = String(loc.line);
const column = String(loc.column);
const context = `${filename}:${line}:${column}`;
throw new Error(`Unhandled node type "${node.type}" at "${context}" in compareKey`);
}
}
}
var TemplateExtractor = class _TemplateExtractor {
ast;
filename;
data;
constructor(ast, filename, data) {
this.ast = ast;
this.filename = filename;
this.data = data;
}
/**
* Create a new [[TemplateExtractor]] from javascript source code.
*
* `Source` offsets will be relative to the string, i.e. offset 0 is the first
* character of the string. If the string is only a subset of a larger string
* the offsets must be adjusted manually.
*
* @param source - Source code.
* @param filename - Optional filename to set in the resulting
* `Source`. Defauls to `"inline"`.
*/
static fromString(source, filename) {
const ast = espree.parse(source, {
ecmaVersion: "latest",
sourceType: "module",
loc: true
});
return new _TemplateExtractor(ast, filename ?? "inline", source);
}
/**
* Extract object properties.
*
* Given a key `"template"` this method finds all objects literals with a
* `"template"` property and creates a [[Source]] instance with proper offsets
* with the value of the property. For instance:
*
* ```
* const myObj = {
* foo: 'bar',
* };
* ```
*
* The above snippet would yield a `Source` with the content `bar`.
*
*/
extractObjectProperty(key) {
const result = [];
const { filename, data } = this;
const node = this.ast;
walk.simple(node, {
Property(node2) {
if (compareKey(node2.key, key, filename)) {
const source = extractLiteral(node2.value, filename, data);
if (source) {
source.filename = filename;
result.push(source);
}
}
}
});
return result;
}
};
export {
TemplateExtractor,
positionFromOffset,
positionToOffset
};
//# sourceMappingURL=index.mjs.map