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 babelParser from '@babel/parser';
|
||||||
import { codeFrameColumns } from '@babel/code-frame';
|
import { codeFrameColumns } from '@babel/code-frame';
|
||||||
import * as babelTraverse from '@babel/traverse';
|
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 { fetchContent } from './content.js';
|
||||||
import { isFetchContent } from './utils.js';
|
import { isFetchContent } from './utils.js';
|
||||||
import { yellow } from 'kleur/colors';
|
import { yellow } from 'kleur/colors';
|
||||||
|
@ -21,6 +21,8 @@ import { isComponentTag } from '../utils';
|
||||||
import { renderMarkdown } from '@astrojs/markdown-support';
|
import { renderMarkdown } from '@astrojs/markdown-support';
|
||||||
import { transform } from '../transform/index.js';
|
import { transform } from '../transform/index.js';
|
||||||
import { PRISM_IMPORT } from '../transform/prism.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;
|
const traverse: typeof babelTraverse.default = (babelTraverse.default as any).default;
|
||||||
|
|
||||||
|
@ -170,14 +172,38 @@ function getComponentWrapper(_name: string, { url, importSpecifier }: ComponentI
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Evaluate expression (safely) */
|
/** 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 {
|
||||||
let { code } = transformSync(raw, {
|
try {
|
||||||
loader: 'tsx',
|
let { code } = transformSync(raw, {
|
||||||
jsxFactory: 'h',
|
loader: 'tsx',
|
||||||
jsxFragment: 'Fragment',
|
jsxFactory: 'h',
|
||||||
charset: 'utf8',
|
jsxFragment: 'Fragment',
|
||||||
});
|
charset: 'utf8'
|
||||||
return code;
|
});
|
||||||
|
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 {
|
interface CompileResult {
|
||||||
|
@ -473,8 +499,11 @@ async function compileHtml(enterNode: TemplateNode, state: CodegenState, compile
|
||||||
raw += children[nextChildIndex++];
|
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?
|
// 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 (!FALSY_EXPRESSIONS.has(code)) {
|
||||||
if (state.markers.insideMarkdown) {
|
if (state.markers.insideMarkdown) {
|
||||||
buffers[curr] += `{${code}}`;
|
buffers[curr] += `{${code}}`;
|
||||||
|
|
|
@ -2,3 +2,87 @@
|
||||||
export function isComponentTag(tag: string) {
|
export function isComponentTag(tag: string) {
|
||||||
return /^[A-Z]/.test(tag) || /^[a-z]+\./.test(tag);
|
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