parent
e7b2a6d8dd
commit
03ebdc3387
2 changed files with 123 additions and 10 deletions
|
@ -13,7 +13,7 @@ import _babelGenerator from '@babel/generator';
|
|||
import babelParser from '@babel/parser';
|
||||
import { codeFrameColumns } from '@babel/code-frame';
|
||||
import * as babelTraverse from '@babel/traverse';
|
||||
import { error, warn } from '../../logger.js';
|
||||
import { error, warn, parseError } from '../../logger.js';
|
||||
import { fetchContent } from './content.js';
|
||||
import { isFetchContent } from './utils.js';
|
||||
import { yellow } from 'kleur/colors';
|
||||
|
@ -21,6 +21,8 @@ import { isComponentTag } from '../utils';
|
|||
import { renderMarkdown } from '@astrojs/markdown-support';
|
||||
import { transform } from '../transform/index.js';
|
||||
import { PRISM_IMPORT } from '../transform/prism.js';
|
||||
import { positionAt } from '../utils';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
const traverse: typeof babelTraverse.default = (babelTraverse.default as any).default;
|
||||
|
||||
|
@ -170,14 +172,38 @@ function getComponentWrapper(_name: string, { url, importSpecifier }: ComponentI
|
|||
}
|
||||
|
||||
/** Evaluate expression (safely) */
|
||||
function compileExpressionSafe(raw: string): string {
|
||||
function compileExpressionSafe(raw: string, { state, compileOptions, location }: { state: CodegenState, compileOptions: CompileOptions, location: { start: number, end: number } }): string|null {
|
||||
try {
|
||||
let { code } = transformSync(raw, {
|
||||
loader: 'tsx',
|
||||
jsxFactory: 'h',
|
||||
jsxFragment: 'Fragment',
|
||||
charset: 'utf8',
|
||||
charset: 'utf8'
|
||||
});
|
||||
return code;
|
||||
} catch ({ errors }) {
|
||||
const err = new Error() as any;
|
||||
const e = errors[0];
|
||||
err.filename = state.filename;
|
||||
const text = readFileSync(state.filename).toString();
|
||||
const start = positionAt(location.start, text);
|
||||
start.line += e.location.line;
|
||||
start.character += e.location.column + 1;
|
||||
err.start = { line: start.line, column: start.character };
|
||||
|
||||
const end = { ...start };
|
||||
end.character += e.location.length;
|
||||
|
||||
const frame = codeFrameColumns(text, {
|
||||
start: { line: start.line, column: start.character },
|
||||
end: { line: end.line, column: end.character },
|
||||
})
|
||||
|
||||
err.frame = frame;
|
||||
err.message = e.text;
|
||||
parseError(compileOptions.logging, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
interface CompileResult {
|
||||
|
@ -473,8 +499,11 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
|||
raw += children[nextChildIndex++];
|
||||
}
|
||||
}
|
||||
const location = { start: node.start, end: node.end };
|
||||
// TODO Do we need to compile this now, or should we compile the entire module at the end?
|
||||
let code = compileExpressionSafe(raw).trim().replace(/\;$/, '');
|
||||
let code = compileExpressionSafe(raw, { state, compileOptions, location });
|
||||
if (code === null) throw new Error(`Unable to compile expression`);
|
||||
code = code.trim().replace(/\;$/, '');
|
||||
if (!FALSY_EXPRESSIONS.has(code)) {
|
||||
if (state.markers.insideMarkdown) {
|
||||
buffers[curr] += `{${code}}`;
|
||||
|
|
|
@ -2,3 +2,87 @@
|
|||
export function isComponentTag(tag: string) {
|
||||
return /^[A-Z]/.test(tag) || /^[a-z]+\./.test(tag);
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
line: number;
|
||||
character: number;
|
||||
}
|
||||
|
||||
/** Clamps a number between min and max */
|
||||
export function clamp(num: number, min: number, max: number): number {
|
||||
return Math.max(min, Math.min(max, num));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line and character based on the offset
|
||||
* @param offset The index of the position
|
||||
* @param text The text for which the position should be retrived
|
||||
*/
|
||||
export function positionAt(offset: number, text: string): Position {
|
||||
offset = clamp(offset, 0, text.length);
|
||||
|
||||
const lineOffsets = getLineOffsets(text);
|
||||
let low = 0;
|
||||
let high = lineOffsets.length;
|
||||
if (high === 0) {
|
||||
return { line: 0, character: offset };
|
||||
}
|
||||
|
||||
while (low < high) {
|
||||
const mid = Math.floor((low + high) / 2);
|
||||
if (lineOffsets[mid] > offset) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// low is the least x for which the line offset is larger than the current offset
|
||||
// or array.length if no line offset is larger than the current offset
|
||||
const line = low - 1;
|
||||
return { line, character: offset - lineOffsets[line] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the offset of the line and character position
|
||||
* @param position Line and character position
|
||||
* @param text The text for which the offset should be retrived
|
||||
*/
|
||||
export function offsetAt(position: Position, text: string): number {
|
||||
const lineOffsets = getLineOffsets(text);
|
||||
|
||||
if (position.line >= lineOffsets.length) {
|
||||
return text.length;
|
||||
} else if (position.line < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const lineOffset = lineOffsets[position.line];
|
||||
const nextLineOffset = position.line + 1 < lineOffsets.length ? lineOffsets[position.line + 1] : text.length;
|
||||
|
||||
return clamp(nextLineOffset, lineOffset, lineOffset + position.character);
|
||||
}
|
||||
|
||||
/** Get the offset of all lines */
|
||||
function getLineOffsets(text: string) {
|
||||
const lineOffsets = [];
|
||||
let isLineStart = true;
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if (isLineStart) {
|
||||
lineOffsets.push(i);
|
||||
isLineStart = false;
|
||||
}
|
||||
const ch = text.charAt(i);
|
||||
isLineStart = ch === '\r' || ch === '\n';
|
||||
if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (isLineStart && text.length > 0) {
|
||||
lineOffsets.push(text.length);
|
||||
}
|
||||
|
||||
return lineOffsets;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue