chore: remove @astrojs/parser (#2845)

* Removed parser from astro

* Removed parser files

* Updated changeset config

* Removed from license
This commit is contained in:
Juan Martín Seery 2022-03-21 14:30:23 -03:00 committed by GitHub
parent 0c5378b8cf
commit 41110ebe72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 5 additions and 4978 deletions

View file

@ -1,8 +1,8 @@
{
"$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json",
"$schema": "https://unpkg.com/@changesets/config@1.7.0/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "withastro/astro" }],
"commit": false,
"linked": [["astro", "@astrojs/parser"]],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",

View file

@ -31,7 +31,6 @@
"@example/with-tailwindcss": "0.0.1",
"@example/with-vite-plugin-pwa": "0.0.1",
"astro": "0.24.3",
"@astrojs/parser": "0.22.2",
"@astrojs/prism": "0.4.0",
"@test/custom-element-renderer": "0.1.0",
"@test/static-build-pkg": "0.0.0",

15
LICENSE
View file

@ -21,21 +21,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
This license applies to parts of the `packages/astro-parser` subdirectory originating from the
https://github.com/sveltejs/svelte repository:
Copyright (c) 2016-21 [these people](https://github.com/sveltejs/svelte/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
"""
This license applies to parts of the `packages/create-astro` and `packages/astro` subdirectories originating from the https://github.com/sveltejs/kit repository:

View file

@ -1,121 +0,0 @@
# @astrojs/parser
## 0.22.2
## 0.22.1
## 0.22.0
### Minor Changes
- [#2202](https://github.com/withastro/astro/pull/2202) [`45cea6ae`](https://github.com/withastro/astro/commit/45cea6aec5a310fed4cb8da0d96670d6b99a2539) Thanks [@jonathantneal](https://github.com/jonathantneal)! - Officially drop support for Node v12. The minimum supported version is now Node v14.15+,
## 0.20.3
## 0.20.2
### Patch Changes
- 5d2ea578: fixed an issue using namespaced attributes in astro files
## 0.18.6
## 0.18.5
### Patch Changes
- cd2b5df4: Prevents locking up checking for --- inside of the HTML portion
## 0.18.0
### Patch Changes
- a7e6666: compile javascript to target Node v12.x
- 294a656: Adds support for global style blocks via `<style global>`
Be careful with this escape hatch! This is best reserved for uses like importing styling libraries like Tailwind, or changing global CSS variables.
- b85e68a: Fixes case where custom elements are not handled within JSX expressions
## 0.18.0-next.5
### Patch Changes
- 294a656: Adds support for global style blocks via `<style global>`
Be careful with this escape hatch! This is best reserved for uses like importing styling libraries like Tailwind, or changing global CSS variables.
## 0.18.0-next.2
### Patch Changes
- a7e6666: compile javascript to target Node v12.x
- b85e68a: Fixes case where custom elements are not handled within JSX expressions
## 0.15.4
### Patch Changes
- 6a660f1: Adds low-level custom element support that renderers can use to enable server side rendering. This will be used in renderers such as a Lit renderer.
## 0.15.0
### Patch Changes
- 47ac2cc: Fix #521, allowing `{...spread}` props to work again
## 0.13.10
### Patch Changes
- 7f8d586: Bugfix: template literals in JSX tags breaking parser
## 0.13.9
### Patch Changes
- f9f2da4: Add repository key to all package.json
## 0.13.8
### Patch Changes
- 490f2be: Add support for Fragments with `<>` and `</>` syntax
## 0.13.3
### Patch Changes
- ab2972b: Update package.json engines for esm support
## 0.12.1
### Patch Changes
- 6de740d: Fix for when there's a parser error with unmatched backticks
## 0.12.0
### Patch Changes
- d2330a5: Improve error display for missing local files
### 0.12.0-next.0
### Patch Changes
- Fixes a few more Markdown issues
## 0.11.0
### Patch Changes
- 9cdada0: Fixes a few edge case bugs with Astro's handling of Markdown content
## 0.1.0
### Minor Changes
- b3886c2: Enhanced **Markdown** support! Markdown processing has been moved from `micromark` to `remark` to prepare Astro for user-provided `remark` plugins _in the future_.
This change also introduces a built-in `<Markdown>` component for embedding Markdown and any Astro-supported component format inside of `.astro` files. [Read more about Astro's Markdown support.](https://docs.astro.build/markdown/)

View file

@ -1,5 +0,0 @@
# `@astrojs/parser`
> ⚠️ **Notice** `@astrojs/parser` has been deprecated. In previous versions of `astro` it was responsible for parsing `.astro` files. `astro` has since adopted the brand new [`@astrojs/compiler`](https://github.com/withastro/compiler).
This directory is a fork of `svelte/compiler`. It is meant to stay as close to the original source as possible, so that upstream changes are easy to integrate. Everything svelte-specific and unrelated to parsing (compiler, preprocess, etc) has been removed.

View file

@ -1,37 +0,0 @@
{
"name": "@astrojs/parser",
"version": "0.22.2",
"type": "commonjs",
"author": "withastro",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/withastro/astro.git",
"directory": "packages/astro-parser"
},
"bugs": "https://github.com/withastro/astro/issues",
"homepage": "https://astro.build",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"prepublish": "pnpm run build",
"build": "astro-scripts build \"src/**/*.ts\" && tsc -p tsconfig.json",
"dev": "astro-scripts dev \"src/**/*.ts\""
},
"dependencies": {
"@types/node": "^14.18.12",
"acorn": "^8.7.0",
"locate-character": "^2.0.5",
"magic-string": "^0.25.9"
},
"devDependencies": {
"astro-scripts": "workspace:*"
},
"engines": {
"node": "^14.15.0 || >=16.0.0",
"npm": ">=6.14.0"
}
}

View file

@ -1,83 +0,0 @@
// @ts-nocheck
const now =
typeof process !== 'undefined' && process.hrtime
? () => {
const t = process.hrtime();
return t[0] * 1e3 + t[1] / 1e6;
}
: () => self.performance.now();
interface Timing {
label: string;
start: number;
end: number;
children: Timing[];
}
/** Format benchmarks */
function collapse_timings(timings) {
const result = {};
timings.forEach((timing) => {
result[timing.label] = Object.assign(
{
total: timing.end - timing.start,
},
timing.children && collapse_timings(timing.children)
);
});
return result;
}
export default class Stats {
start_time: number;
current_timing: Timing;
current_children: Timing[];
timings: Timing[];
stack: Timing[];
constructor() {
this.start_time = now();
this.stack = [];
this.current_children = this.timings = [];
}
start(label) {
const timing = {
label,
start: now(),
end: null,
children: [],
};
this.current_children.push(timing);
this.stack.push(timing);
this.current_timing = timing;
this.current_children = timing.children;
}
stop(label) {
if (label !== this.current_timing.label) {
throw new Error(`Mismatched timing labels (expected ${this.current_timing.label}, got ${label})`);
}
this.current_timing.end = now();
this.stack.pop();
this.current_timing = this.stack[this.stack.length - 1];
this.current_children = this.current_timing ? this.current_timing.children : this.timings;
}
render() {
const timings = Object.assign(
{
total: now() - this.start_time,
},
collapse_timings(this.timings)
);
return {
timings,
};
}
}

View file

@ -1 +0,0 @@
export const test = typeof process !== 'undefined' && process.env.TEST;

View file

@ -1,3 +0,0 @@
export * from './interfaces';
export * from './parse/utils/features';
export { default as parse } from './parse/index.js';

View file

@ -1,163 +0,0 @@
import type { SourceMap } from 'magic-string';
export { CompileError } from './utils/error';
export interface BaseNode {
start: number;
end: number;
type: string;
children?: TemplateNode[];
[prop_name: string]: any;
}
export interface Fragment extends BaseNode {
type: 'Fragment';
children: TemplateNode[];
}
export interface Text extends BaseNode {
type: 'Text';
data: string;
raw: string;
}
export interface CodeFence extends BaseNode {
type: 'CodeFence';
metadata: string;
data: string;
raw: string;
}
export interface CodeSpan extends BaseNode {
type: 'CodeFence';
metadata: string;
data: string;
raw: string;
}
export interface Attribute extends BaseNode {
type: 'Attribute';
name: string;
value: Text[];
}
export interface MustacheTag extends BaseNode {
type: 'MustacheTag';
content: string;
}
export type DirectiveType = 'Action' | 'Animation' | 'Binding' | 'Class' | 'EventHandler' | 'Let' | 'Ref' | 'Transition';
interface BaseDirective extends BaseNode {
type: DirectiveType;
expression: null | Node;
name: string;
modifiers: string[];
}
export interface Transition extends BaseDirective {
type: 'Transition';
intro: boolean;
outro: boolean;
}
export type Directive = BaseDirective | Transition;
export type TemplateNode = Text | CodeSpan | CodeFence | MustacheTag | BaseNode | Directive | Transition;
export interface Expression {
type: 'Expression';
start: number;
end: number;
codeChunks: string[];
children: BaseNode[];
}
export interface Parser {
readonly template: string;
readonly filename?: string;
index: number;
stack: Node[];
html: Node;
css: Node;
js: Node;
meta_tags: Map<string, string>;
}
export interface Script extends BaseNode {
type: 'Script';
context: 'runtime' | 'setup';
content: string;
}
export interface Style extends BaseNode {
type: 'Style';
attributes: any[]; // TODO
content: {
start: number;
end: number;
styles: string;
};
}
export interface Ast {
html: TemplateNode;
css: Style[];
module: Script;
// instance: Script;
meta: {
features: number;
};
}
export interface Warning {
start?: { line: number; column: number; pos?: number };
end?: { line: number; column: number };
pos?: number;
code: string;
message: string;
filename?: string;
frame?: string;
toString: () => string;
}
export type ModuleFormat = 'esm' | 'cjs';
export type CssHashGetter = (args: { name: string; filename: string | undefined; css: string; hash: (input: string) => string }) => string;
export interface Visitor {
enter: (node: Node) => void;
leave?: (node: Node) => void;
}
export interface AppendTarget {
slots: Record<string, string>;
slot_stack: string[];
}
export interface Var {
name: string;
export_name?: string; // the `bar` in `export { foo as bar }`
injected?: boolean;
module?: boolean;
mutated?: boolean;
reassigned?: boolean;
referenced?: boolean; // referenced from template scope
referenced_from_script?: boolean; // referenced from script
writable?: boolean;
// used internally, but not exposed
global?: boolean;
internal?: boolean; // event handlers, bindings
initialised?: boolean;
hoistable?: boolean;
subscribable?: boolean;
is_reactive_dependency?: boolean;
imported?: boolean;
}
export interface CssResult {
code: string;
map: SourceMap;
}

View file

@ -1,262 +0,0 @@
// @ts-nocheck
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import fragment from './state/fragment.js';
import { whitespace } from '../utils/patterns.js';
import { reserved } from '../utils/names.js';
import full_char_code_at from '../utils/full_char_code_at.js';
import { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces.js';
import error from '../utils/error.js';
type ParserState = (parser: Parser) => ParserState | void;
interface LastAutoClosedTag {
tag: string;
reason: string;
depth: number;
}
export class Parser {
readonly template: string;
readonly filename?: string;
readonly customElement: boolean;
index = 0;
stack: TemplateNode[] = [];
html: Fragment;
css: Style[] = [];
js: Script[] = [];
meta_tags = {};
last_auto_closed_tag?: LastAutoClosedTag;
feature_flags = 0;
constructor(template: string, options: ParserOptions) {
if (typeof template !== 'string') {
throw new TypeError('Template must be a string');
}
this.template = template.replace(/\s+$/, '');
this.filename = options.filename;
this.customElement = options.customElement;
this.html = {
start: null,
end: null,
type: 'Fragment',
children: [],
};
this.stack.push(this.html);
let state: ParserState = fragment;
while (this.index < this.template.length) {
state = state(this) || fragment;
}
if (this.stack.length > 1) {
const current = this.current();
const type = current.type === 'Element' ? `<${current.name}>` : 'Block';
const slug = current.type === 'Element' ? 'element' : 'block';
this.error(
{
code: `unclosed-${slug}`,
message: `${type} was left open`,
},
current.start
);
}
if (state !== fragment) {
this.error({
code: 'unexpected-eof',
message: 'Unexpected end of input',
});
}
if (this.html.children.length) {
let start = this.html.children[0].start;
while (whitespace.test(template[start])) start += 1;
let end = this.html.children[this.html.children.length - 1].end;
while (whitespace.test(template[end - 1])) end -= 1;
this.html.start = start;
this.html.end = end;
} else {
this.html.start = this.html.end = null;
}
}
current() {
return this.stack[this.stack.length - 1];
}
acorn_error(err: any) {
this.error(
{
code: 'parse-error',
message: err.message.replace(/ \(\d+:\d+\)$/, ''),
},
err.pos
);
}
error({ code, message }: { code: string; message: string }, index = this.index) {
error(this.template, message, {
name: 'ParseError',
code,
source: this.template,
start: index,
filename: this.filename,
});
}
eat(str: string, required?: boolean, message?: string) {
if (this.match(str)) {
this.index += str.length;
return true;
}
if (required) {
this.error({
code: `unexpected-${this.index === this.template.length ? 'eof' : 'token'}`,
message: message || `Expected ${str}`,
});
}
return false;
}
match(str: string) {
return this.template.slice(this.index, this.index + str.length) === str;
}
match_regex(pattern: RegExp) {
const match = pattern.exec(this.template.slice(this.index));
if (!match || match.index !== 0) return null;
return match[0];
}
allow_whitespace() {
while (this.index < this.template.length && whitespace.test(this.template[this.index])) {
this.index++;
}
}
read(pattern: RegExp) {
const result = this.match_regex(pattern);
if (result) this.index += result.length;
return result;
}
read_identifier(allow_reserved = false) {
const start = this.index;
let i = this.index;
const code = full_char_code_at(this.template, i);
if (!isIdentifierStart(code, true)) return null;
i += code <= 0xffff ? 1 : 2;
while (i < this.template.length) {
const code = full_char_code_at(this.template, i);
if (!isIdentifierChar(code, true)) break;
i += code <= 0xffff ? 1 : 2;
}
const identifier = this.template.slice(this.index, (this.index = i));
if (!allow_reserved && reserved.has(identifier)) {
this.error(
{
code: 'unexpected-reserved-word',
message: `'${identifier}' is a reserved word in JavaScript and cannot be used here`,
},
start
);
}
return identifier;
}
read_until(pattern: RegExp) {
if (this.index >= this.template.length) {
this.error({
code: 'unexpected-eof',
message: 'Unexpected end of input',
});
}
const start = this.index;
const match = pattern.exec(this.template.slice(start));
if (match) {
this.index = start + match.index;
return this.template.slice(start, this.index);
}
this.index = this.template.length;
return this.template.slice(start);
}
require_whitespace() {
if (!whitespace.test(this.template[this.index])) {
this.error({
code: 'missing-whitespace',
message: 'Expected whitespace',
});
}
this.allow_whitespace();
}
}
/**
* Parse
* Step 1/3 in Astro SSR.
* This is the first pass over .astro files and the step at which we convert a string to an AST for us to crawl.
*/
export default function parse(template: string, options: ParserOptions = {}): Ast {
const parser = new Parser(template, options);
// const instance_scripts = parser.js.filter((script) => script.context === 'default');
// const module_scripts = parser.js.filter((script) => script.context === 'module');
const astro_scripts = parser.js.filter((script) => script.context === 'setup');
if (astro_scripts.length > 1) {
parser.error(
{
code: 'invalid-script',
message: 'A component can only have one frontmatter (---) script',
},
astro_scripts[1].start
);
}
// if (module_scripts.length > 1) {
// parser.error(
// {
// code: 'invalid-script',
// message: 'A component can only have one <script context="module"> element',
// },
// module_scripts[1].start
// );
// }
return {
html: parser.html,
css: parser.css,
// instance: instance_scripts[0],
module: astro_scripts[0],
meta: {
features: parser.feature_flags,
},
};
}

View file

@ -1,72 +0,0 @@
// @ts-nocheck
import { Parser } from '../index.js';
import { isIdentifierStart } from 'acorn';
import full_char_code_at from '../../utils/full_char_code_at.js';
import { is_bracket_open, is_bracket_close, is_bracket_pair, get_bracket_close } from '../utils/bracket.js';
import { parse_expression_at } from './expression.js';
import { Pattern } from 'estree';
export default function read_context(parser: Parser): Pattern & { start: number; end: number } {
const start = parser.index;
let i = parser.index;
const code = full_char_code_at(parser.template, i);
if (isIdentifierStart(code, true)) {
return {
type: 'Identifier',
name: parser.read_identifier(),
start,
end: parser.index,
};
}
if (!is_bracket_open(code)) {
parser.error({
code: 'unexpected-token',
message: 'Expected identifier or destructure pattern',
});
}
const bracket_stack = [code];
i += code <= 0xffff ? 1 : 2;
while (i < parser.template.length) {
const code = full_char_code_at(parser.template, i);
if (is_bracket_open(code)) {
bracket_stack.push(code);
} else if (is_bracket_close(code)) {
if (!is_bracket_pair(bracket_stack[bracket_stack.length - 1], code)) {
parser.error({
code: 'unexpected-token',
message: `Expected ${String.fromCharCode(get_bracket_close(bracket_stack[bracket_stack.length - 1]))}`,
});
}
bracket_stack.pop();
if (bracket_stack.length === 0) {
i += code <= 0xffff ? 1 : 2;
break;
}
}
i += code <= 0xffff ? 1 : 2;
}
parser.index = i;
const pattern_string = parser.template.slice(start, i);
try {
// the length of the `space_with_newline` has to be start - 1
// because we added a `(` in front of the pattern_string,
// which shifted the entire string to right by 1
// so we offset it by removing 1 character in the `space_with_newline`
// to achieve that, we remove the 1st space encountered,
// so it will not affect the `column` of the node
let space_with_newline = parser.template.slice(0, start).replace(/[^\n]/g, ' ');
const first_space = space_with_newline.indexOf(' ');
space_with_newline = space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
return (parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, start - 1) as any).left;
} catch (error) {
parser.acorn_error(error);
}
}

View file

@ -1,254 +0,0 @@
import type { BaseNode, Expression } from '../../interfaces';
import { Parser } from '../index.js';
import parseAstro from '../index.js';
interface ParseState {
source: string;
start: number;
index: number;
curlyCount: number;
bracketCount: number;
root: Expression;
parser: Parser;
}
function peek_char(state: ParseState) {
return state.source[state.index];
}
function peek_nonwhitespace(state: ParseState) {
let index = state.index;
do {
let char = state.source[index];
if (!/\s/.test(char)) {
return char;
}
index++;
} while (index < state.source.length);
}
function next_char(state: ParseState) {
return state.source[state.index++];
}
function in_bounds(state: ParseState) {
return state.index < state.source.length;
}
function consume_string(state: ParseState, stringChar: string) {
let inEscape;
do {
const char = next_char(state);
if (inEscape) {
inEscape = false;
} else if (char === '\\') {
inEscape = true;
} else if (char === stringChar) {
break;
}
} while (in_bounds(state));
}
function consume_multiline_comment(state: ParseState) {
do {
const char = next_char(state);
if (char === '*' && peek_char(state) === '/') {
break;
}
} while (in_bounds(state));
}
function consume_line_comment(state: ParseState) {
do {
const char = next_char(state);
if (char === '\n') {
break;
}
} while (in_bounds(state));
}
const voidElements = new Set(['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
function consume_tag(state: ParseState) {
const start = state.index - 1;
let tagName = '';
let inTag = false;
let inStart = true;
let selfClosed = false;
let inClose = false;
let bracketIndex = 1;
do {
const char = next_char(state);
switch (char) {
case "'":
case '"':
case '`': {
consume_string(state, char);
break;
}
case '<': {
inTag = false;
tagName = '';
if (peek_nonwhitespace(state) === '/') {
inClose = true;
bracketIndex--;
} else {
inStart = true;
bracketIndex++;
}
break;
}
case '>': {
// An arrow function, probably
if (!inStart && !inClose) {
break;
}
bracketIndex--;
const addExpectedBrackets =
// Void elements don't need a closing
!voidElements.has(tagName.toLowerCase()) &&
// Self-closing don't need a closing
!selfClosed &&
// If we're in a start tag, we expect to find 2 more brackets
!inClose;
if (addExpectedBrackets) {
bracketIndex += 2;
}
inTag = false;
selfClosed = false;
inStart = false;
inClose = false;
break;
}
case ' ': {
inTag = true;
break;
}
case '/': {
if (inStart) {
selfClosed = true;
}
break;
}
default: {
if (!inTag) {
tagName += char;
}
break;
}
}
// Unclosed tags
if (state.curlyCount <= 0) {
break;
}
if (bracketIndex === 0) {
break;
}
} while (in_bounds(state));
const source = state.source.substring(start, state.index);
const ast = parseAstro(source);
state.parser.feature_flags |= ast.meta.features;
const fragment = ast.html;
return fragment;
}
function consume_expression(parser: Parser, source: string, start: number): Expression {
const expr: Expression = {
type: 'Expression',
start,
end: Number.NaN,
codeChunks: [],
children: [],
};
let codeStart: number = start;
const state: ParseState = {
source,
start,
index: start,
curlyCount: 1,
bracketCount: 0,
root: expr,
parser,
};
do {
const char = next_char(state);
switch (char) {
case '{': {
state.curlyCount++;
break;
}
case '}': {
state.curlyCount--;
break;
}
case '<': {
const chunk = source.substring(codeStart, state.index - 1);
expr.codeChunks.push(chunk);
const tag = consume_tag(state);
expr.children.push(tag);
codeStart = state.index;
break;
}
case "'":
case '"':
case '`': {
consume_string(state, char);
break;
}
case '/': {
switch (peek_char(state)) {
case '/': {
consume_line_comment(state);
break;
}
case '*': {
consume_multiline_comment(state);
break;
}
}
}
}
} while (in_bounds(state) && state.curlyCount > 0);
expr.end = state.index - 1;
if (expr.children.length || !expr.codeChunks.length) {
expr.codeChunks.push(source.substring(codeStart, expr.end));
}
return expr;
}
export const parse_expression_at = (parser: Parser, source: string, index: number): Expression => {
const expression = consume_expression(parser, source, index);
return expression;
};
export default function read_expression(parser: Parser) {
try {
const expression = parse_expression_at(parser, parser.template, parser.index);
parser.index = expression.end;
return expression;
} catch (err) {
parser.acorn_error(err);
}
}

View file

@ -1,60 +0,0 @@
// @ts-nocheck
import type { Node } from 'estree';
import { Parser } from '../index.js';
import { Script } from '../../interfaces.js';
const script_closing_tag = '</script>';
function get_context(parser: Parser, attributes: any[], start: number): 'runtime' | 'setup' {
const context = attributes.find((attribute) => attribute.name === 'astro');
if (!context) return 'runtime';
if (context.value === true) return 'setup';
if (context.value.length !== 1 || context.value[0].type !== 'Text') {
parser.error(
{
code: 'invalid-script',
message: 'astro attribute must be static',
},
start
);
}
const value = context.value[0].data;
if (value !== 'setup') {
parser.error(
{
code: 'invalid-script',
message: 'If the "astro" attribute has a value, its value must be "setup"',
},
context.start
);
}
return value;
}
export default function read_script(parser: Parser, start: number, attributes: Node[]): Script {
const script_start = parser.index;
const script_end = parser.template.indexOf(script_closing_tag, script_start);
if (script_end === -1) {
parser.error({
code: 'unclosed-script',
message: '<script> must have a closing tag',
});
}
const source = parser.template.slice(0, script_start).replace(/[^\n]/g, ' ') + parser.template.slice(script_start, script_end);
parser.index = script_end + script_closing_tag.length;
return {
type: 'Script',
start,
end: parser.index,
context: get_context(parser, attributes, start),
content: source,
};
}

View file

@ -1,40 +0,0 @@
import { Parser } from '../index.js';
import { Style } from '../../interfaces.js';
interface Attribute {
start: number;
end: number;
type: 'Attribute';
name: string;
value: {
raw: string;
data: string;
}[];
}
export default function read_style(parser: Parser, start: number, attributes: Attribute[]): Style {
const content_start = parser.index;
const styles = parser.read_until(/<\/style>/);
const content_end = parser.index;
parser.eat('</style>', true);
const end = parser.index;
return {
type: 'Style',
start,
end,
attributes,
content: {
start: content_start,
end: content_end,
styles,
},
};
}
function is_ref_selector(a: any, b: any) {
// TODO add CSS node types
if (!b) return false;
return a.type === 'TypeSelector' && a.name === 'ref' && b.type === 'PseudoClassSelector';
}

View file

@ -1,38 +0,0 @@
// @ts-nocheck
import { Parser } from '../index.js';
export default function codefence(parser: Parser) {
const start = parser.index;
const open = parser.match_regex(/[`~]{3,}/);
parser.index += open!.length;
let raw = open + '';
while (parser.index < parser.template.length && !parser.match(open)) {
raw += parser.template[parser.index++];
}
parser.eat(open, true);
raw += open;
const trailingWhitespace = parser.read_until(/\S/);
const { metadata, data } = extractCodeFence(raw);
const node = {
start,
end: parser.index,
type: 'CodeFence',
raw: `${raw}` + trailingWhitespace,
metadata,
data,
};
parser.current().children.push(node);
}
/** Extract attributes on first line */
function extractCodeFence(str: string) {
const [_, leadingLine] = str.match(/(^[^\n]*\r?\n)/m) ?? ['', ''];
const metadata = leadingLine.trim();
const data = str.slice(leadingLine.length);
return { metadata, data };
}

View file

@ -1,28 +0,0 @@
// @ts-nocheck
import { Parser } from '../index.js';
export default function codespan(parser: Parser) {
const start = parser.index;
const open = parser.match_regex(/(?<!\\)`{1,2}/);
parser.index += open!.length;
let raw = open;
while (parser.index < parser.template.length && !parser.match(open)) {
raw += parser.template[parser.index++];
}
parser.eat(open, true);
raw += open;
const node = {
start,
end: parser.index,
type: 'CodeSpan',
raw,
data: raw
?.slice(open?.length, open?.length * -1)
.replace(/^ /, '')
.replace(/ $/, ''),
};
parser.current().children.push(node);
}

View file

@ -1,32 +0,0 @@
import tag from './tag.js';
import setup from './setup.js';
import mustache from './mustache.js';
import text from './text.js';
import codefence from './codefence.js';
import codespan from './codespan.js';
import { Parser } from '../index.js';
export default function fragment(parser: Parser) {
if (parser.html.children.length === 0 && parser.match_regex(/^---/m)) {
return setup;
}
// Fenced code blocks are pretty complex in the GFM spec
// https://github.github.com/gfm/#fenced-code-blocks
if (parser.match_regex(/[`~]{3,}/)) {
return codefence;
}
if (parser.match_regex(/(?<!\\)`{1,2}/)) {
return codespan;
}
if (parser.match('<')) {
return tag;
}
if (parser.match('{')) {
return mustache;
}
return text;
}

View file

@ -1,412 +0,0 @@
import read_context from '../read/context.js';
import read_expression from '../read/expression.js';
import { closing_tag_omitted } from '../utils/html.js';
import { whitespace } from '../../utils/patterns.js';
import { to_string } from '../utils/node.js';
import { Parser } from '../index.js';
import { TemplateNode } from '../../interfaces.js';
type TODO = any;
function trim_whitespace(block: TemplateNode, trim_before: boolean, trim_after: boolean) {
if (!block.children || block.children.length === 0) return; // AwaitBlock
const first_child = block.children[0];
const last_child = block.children[block.children.length - 1];
if (first_child.type === 'Text' && trim_before) {
first_child.data = first_child.data.trimStart();
if (!first_child.data) block.children.shift();
}
if (last_child.type === 'Text' && trim_after) {
last_child.data = last_child.data.trimEnd();
if (!last_child.data) block.children.pop();
}
if (block.else) {
trim_whitespace(block.else, trim_before, trim_after);
}
if (first_child.elseif) {
trim_whitespace(first_child, trim_before, trim_after);
}
}
export default function mustache(parser: Parser) {
const start = parser.index;
parser.index += 1;
parser.allow_whitespace();
// {/if}, {/each}, {/await} or {/key}
if (parser.eat('/')) {
let block = parser.current();
let expected: TODO;
if (closing_tag_omitted(block.name)) {
block.end = start;
parser.stack.pop();
block = parser.current();
}
if (block.type === 'ElseBlock' || block.type === 'PendingBlock' || block.type === 'ThenBlock' || block.type === 'CatchBlock') {
block.end = start;
parser.stack.pop();
block = parser.current();
expected = 'await';
}
if (block.type === 'IfBlock') {
expected = 'if';
} else if (block.type === 'EachBlock') {
expected = 'each';
} else if (block.type === 'AwaitBlock') {
expected = 'await';
} else if (block.type === 'KeyBlock') {
expected = 'key';
} else {
parser.error({
code: 'unexpected-block-close',
message: 'Unexpected block closing tag',
});
}
parser.eat(expected, true);
parser.allow_whitespace();
parser.eat('}', true);
while (block.elseif) {
block.end = parser.index;
parser.stack.pop();
block = parser.current();
if (block.else) {
block.else.end = start;
}
}
// strip leading/trailing whitespace as necessary
const char_before = parser.template[block.start - 1];
const char_after = parser.template[parser.index];
const trim_before = !char_before || whitespace.test(char_before);
const trim_after = !char_after || whitespace.test(char_after);
trim_whitespace(block, trim_before, trim_after);
block.end = parser.index;
parser.stack.pop();
} else if (parser.eat(':else')) {
if (parser.eat('if')) {
parser.error({
code: 'invalid-elseif',
message: "'elseif' should be 'else if'",
});
}
parser.allow_whitespace();
// :else if
if (parser.eat('if')) {
const block = parser.current();
if (block.type !== 'IfBlock') {
parser.error({
code: 'invalid-elseif-placement',
message: parser.stack.some((block) => block.type === 'IfBlock')
? `Expected to close ${to_string(block)} before seeing {:else if ...} block`
: 'Cannot have an {:else if ...} block outside an {#if ...} block',
});
}
parser.require_whitespace();
const expression = read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);
block.else = {
start: parser.index,
end: null,
type: 'ElseBlock',
children: [
{
start: parser.index,
end: null,
type: 'IfBlock',
elseif: true,
expression,
children: [],
},
],
};
parser.stack.push(block.else.children[0]);
} else {
// :else
const block = parser.current();
if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
parser.error({
code: 'invalid-else-placement',
message: parser.stack.some((block) => block.type === 'IfBlock' || block.type === 'EachBlock')
? `Expected to close ${to_string(block)} before seeing {:else} block`
: 'Cannot have an {:else} block outside an {#if ...} or {#each ...} block',
});
}
parser.allow_whitespace();
parser.eat('}', true);
block.else = {
start: parser.index,
end: null,
type: 'ElseBlock',
children: [],
};
parser.stack.push(block.else);
}
} else if (parser.match(':then') || parser.match(':catch')) {
const block = parser.current();
const is_then = parser.eat(':then') || !parser.eat(':catch');
if (is_then) {
if (block.type !== 'PendingBlock') {
parser.error({
code: 'invalid-then-placement',
message: parser.stack.some((block) => block.type === 'PendingBlock')
? `Expected to close ${to_string(block)} before seeing {:then} block`
: 'Cannot have an {:then} block outside an {#await ...} block',
});
}
} else {
if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
parser.error({
code: 'invalid-catch-placement',
message: parser.stack.some((block) => block.type === 'ThenBlock' || block.type === 'PendingBlock')
? `Expected to close ${to_string(block)} before seeing {:catch} block`
: 'Cannot have an {:catch} block outside an {#await ...} block',
});
}
}
block.end = start;
parser.stack.pop();
const await_block = parser.current();
if (!parser.eat('}')) {
parser.require_whitespace();
await_block[is_then ? 'value' : 'error'] = read_context(parser);
parser.allow_whitespace();
parser.eat('}', true);
}
const new_block: TemplateNode = {
start,
// @ts-ignore
end: null,
type: is_then ? 'ThenBlock' : 'CatchBlock',
children: [],
skip: false,
};
await_block[is_then ? 'then' : 'catch'] = new_block;
parser.stack.push(new_block);
} else if (parser.eat('#')) {
// {#if foo}, {#each foo} or {#await foo}
let type;
if (parser.eat('if')) {
type = 'IfBlock';
} else if (parser.eat('each')) {
type = 'EachBlock';
} else if (parser.eat('await')) {
type = 'AwaitBlock';
} else if (parser.eat('key')) {
type = 'KeyBlock';
} else {
parser.error({
code: 'expected-block-type',
message: 'Expected if, each, await or key',
});
}
parser.require_whitespace();
const expression = read_expression(parser);
// @ts-ignore
const block: TemplateNode =
type === 'AwaitBlock'
? {
start,
end: null,
type,
expression,
value: null,
error: null,
pending: {
start: null,
end: null,
type: 'PendingBlock',
children: [],
skip: true,
},
then: {
start: null,
end: null,
type: 'ThenBlock',
children: [],
skip: true,
},
catch: {
start: null,
end: null,
type: 'CatchBlock',
children: [],
skip: true,
},
}
: {
start,
end: null,
type,
expression,
children: [],
};
parser.allow_whitespace();
// {#each} blocks must declare a context {#each list as item}
if (type === 'EachBlock') {
parser.eat('as', true);
parser.require_whitespace();
block.context = read_context(parser);
parser.allow_whitespace();
if (parser.eat(',')) {
parser.allow_whitespace();
block.index = parser.read_identifier();
if (!block.index) {
parser.error({
code: 'expected-name',
message: 'Expected name',
});
}
parser.allow_whitespace();
}
if (parser.eat('(')) {
parser.allow_whitespace();
block.key = read_expression(parser);
parser.allow_whitespace();
parser.eat(')', true);
parser.allow_whitespace();
}
}
const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
if (await_block_shorthand) {
parser.require_whitespace();
block.value = read_context(parser);
parser.allow_whitespace();
}
const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch');
if (await_block_catch_shorthand) {
parser.require_whitespace();
block.error = read_context(parser);
parser.allow_whitespace();
}
parser.eat('}', true);
// @ts-ignore
parser.current().children.push(block);
parser.stack.push(block);
if (type === 'AwaitBlock') {
let child_block;
if (await_block_shorthand) {
block.then.skip = false;
child_block = block.then;
} else if (await_block_catch_shorthand) {
block.catch.skip = false;
child_block = block.catch;
} else {
block.pending.skip = false;
child_block = block.pending;
}
child_block.start = parser.index;
parser.stack.push(child_block);
}
} else if (parser.eat('@html')) {
// {@html content} tag
parser.require_whitespace();
const expression = read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);
// @ts-ignore
parser.current().children.push({
start,
end: parser.index,
type: 'RawMustacheTag',
expression,
});
} else if (parser.eat('@debug')) {
// let identifiers;
// // Implies {@debug} which indicates "debug all"
// if (parser.read(/\s*}/)) {
// identifiers = [];
// } else {
// const expression = read_expression(parser);
// identifiers = expression.type === 'SequenceExpression'
// ? expression.expressions
// : [expression];
// identifiers.forEach(node => {
// if (node.type !== 'Identifier') {
// parser.error({
// code: 'invalid-debug-args',
// message: '{@debug ...} arguments must be identifiers, not arbitrary expressions'
// }, node.start);
// }
// });
// parser.allow_whitespace();
// parser.eat('}', true);
// }
// parser.current().children.push({
// start,
// end: parser.index,
// type: 'DebugTag',
// identifiers
// });
throw new Error('@debug not yet supported');
} else {
const expression = read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);
// @ts-ignore
parser.current().children.push({
start,
end: parser.index,
type: 'MustacheTag',
expression,
});
}
}

View file

@ -1,35 +0,0 @@
// @ts-nocheck
import { Parser } from '../index.js';
export default function setup(parser: Parser): void {
// TODO: Error if not at top of file? currently, we ignore / just treat as text.
// if (parser.html.children.length > 0) {
// parser.error({
// code: 'unexpected-token',
// message: 'Frontmatter scripts only supported at the top of file.',
// });
// }
const start = parser.index;
parser.index += 3;
const content_start = parser.index;
const setupScriptContent = parser.read_until(/^---/m);
const content_end = parser.index;
parser.eat('---', true);
const end = parser.index;
parser.js.push({
type: 'Script',
context: 'setup',
start,
end,
content: setupScriptContent,
// attributes,
// content: {
// start: content_start,
// end: content_end,
// styles,
// },
});
return;
}

View file

@ -1,499 +0,0 @@
// @ts-nocheck
import read_expression from '../read/expression.js';
import read_style from '../read/style.js';
import { decode_character_references, closing_tag_omitted } from '../utils/html.js';
import { is_void } from '../../utils/names.js';
import { Parser } from '../index.js';
import { TemplateNode, Text } from '../../interfaces.js';
import fuzzymatch from '../../utils/fuzzymatch.js';
import list from '../../utils/list.js';
import { FEATURE_CUSTOM_ELEMENT } from '../utils/features.js';
const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const meta_tags = new Map([
['astro:head', 'Head'],
['', 'SlotTemplate'],
// ['astro:options', 'Options'],
// ['astro:window', 'Window'],
// ['astro:body', 'Body'],
]);
const valid_meta_tags = Array.from(meta_tags.keys()); //.concat('astro:self', 'astro:component', 'astro:fragment');
const specials = new Map([
// Now handled as "setup" in setup.ts
// [
// 'script',
// {
// read: read_script,
// property: 'js',
// },
// ],
[
'style',
{
read: read_style,
property: 'css',
},
],
]);
const SELF = /^astro:self(?=[\s/>])/;
const COMPONENT = /^astro:component(?=[\s/>])/;
const SLOT = /^astro:fragment(?=[\s/>])/;
const HEAD = /^head(?=[\s/>])/;
const CUSTOM_ELEMENT = /-/;
function parent_is_head(stack) {
let i = stack.length;
while (i--) {
const { type } = stack[i];
if (type === 'Head') return true;
if (type === 'Element' || type === 'InlineComponent') return false;
}
return false;
}
export default function tag(parser: Parser) {
const start = parser.index++;
let parent = parser.current();
if (parser.eat('!--')) {
const data = parser.read_until(/-->/);
parser.eat('-->', true, 'comment was left open, expected -->');
parser.current().children.push({
start,
end: parser.index,
type: 'Comment',
data,
});
return;
}
const is_closing_tag = parser.eat('/');
const name = read_tag_name(parser);
if (CUSTOM_ELEMENT.test(name)) {
parser.feature_flags |= FEATURE_CUSTOM_ELEMENT;
}
if (meta_tags.has(name)) {
const slug = meta_tags.get(name).toLowerCase();
if (is_closing_tag) {
if ((name === 'astro:window' || name === 'astro:body') && parser.current().children.length) {
parser.error(
{
code: `invalid-${slug}-content`,
message: `<${name}> cannot have children`,
},
parser.current().children[0].start
);
}
} else {
if (name in parser.meta_tags) {
parser.error(
{
code: `duplicate-${slug}`,
message: `A component can only have one <${name}> tag`,
},
start
);
}
if (parser.stack.length > 1) {
parser.error(
{
code: `invalid-${slug}-placement`,
message: `<${name}> tags cannot be inside elements or blocks`,
},
start
);
}
parser.meta_tags[name] = true;
}
}
const type = meta_tags.has(name)
? meta_tags.get(name)
: /[A-Z]/.test(name[0]) || name === 'astro:self' || name === 'astro:component'
? 'InlineComponent'
: name === ''
? 'SlotTemplate'
: name === 'title' && parent_is_head(parser.stack)
? 'Title'
: name === 'slot' && !parser.customElement
? 'Slot'
: 'Element';
const element: TemplateNode = {
start,
end: null, // filled in later
type,
name,
attributes: [],
children: [],
};
parser.allow_whitespace();
if (is_closing_tag) {
if (is_void(name)) {
parser.error(
{
code: 'invalid-void-content',
message: `<${name}> is a void element and cannot have children, or a closing tag`,
},
start
);
}
parser.eat('>', true);
// close any elements that don't have their own closing tags, e.g. <div><p></div>
while (parent.name !== name) {
if (parent.type !== 'Element') {
const message =
parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name
? `</${name}> attempted to close <${name}> that was already automatically closed by <${parser.last_auto_closed_tag.reason}>`
: `</${name}> attempted to close an element that was not open`;
parser.error(
{
code: 'invalid-closing-tag',
message,
},
start
);
}
parent.end = start;
parser.stack.pop();
parent = parser.current();
}
parent.end = parser.index;
parser.stack.pop();
if (parser.last_auto_closed_tag && parser.stack.length < parser.last_auto_closed_tag.depth) {
parser.last_auto_closed_tag = null;
}
return;
} else if (closing_tag_omitted(parent.name, name)) {
parent.end = start;
parser.stack.pop();
parser.last_auto_closed_tag = {
tag: parent.name,
reason: name,
depth: parser.stack.length,
};
}
const unique_names: Set<string> = new Set();
let attribute;
while ((attribute = read_attribute(parser, unique_names))) {
element.attributes.push(attribute);
parser.allow_whitespace();
}
if (name === 'astro:component') {
const index = element.attributes.findIndex((attr) => attr.type === 'Attribute' && attr.name === 'this');
if (!~index) {
parser.error(
{
code: 'missing-component-definition',
message: "<astro:component> must have a 'this' attribute",
},
start
);
}
const definition = element.attributes.splice(index, 1)[0];
if (definition.value === true || definition.value.length !== 1 || definition.value[0].type === 'Text') {
parser.error(
{
code: 'invalid-component-definition',
message: 'invalid component definition',
},
definition.start
);
}
element.expression = definition.value[0].expression;
}
// special cases top-level <script> and <style>
if (specials.has(name) && parser.stack.length === 1) {
const special = specials.get(name);
parser.eat('>', true);
const content = special.read(parser, start, element.attributes);
if (content) parser[special.property].push(content);
return;
}
parser.current().children.push(element);
const self_closing = parser.eat('/') || is_void(name);
parser.eat('>', true);
if (self_closing) {
// don't push self-closing elements onto the stack
element.end = parser.index;
} else if (name === 'textarea') {
// special case
element.children = read_sequence(parser, () => parser.template.slice(parser.index, parser.index + 11) === '</textarea>');
parser.read(/<\/textarea>/);
element.end = parser.index;
} else if (name === 'script' || name === 'style') {
// special case
const start = parser.index;
const data = parser.read_until(new RegExp(`</${name}>`));
const end = parser.index;
element.children.push({ start, end, type: 'Text', data });
parser.eat(`</${name}>`, true);
element.end = parser.index;
} else {
parser.stack.push(element);
}
}
function read_tag_name(parser: Parser) {
const start = parser.index;
if (parser.read(SELF)) {
// check we're inside a block, otherwise this
// will cause infinite recursion
let i = parser.stack.length;
let legal = false;
while (i--) {
const fragment = parser.stack[i];
if (fragment.type === 'IfBlock' || fragment.type === 'EachBlock' || fragment.type === 'InlineComponent') {
legal = true;
break;
}
}
if (!legal) {
parser.error(
{
code: 'invalid-self-placement',
message: '<astro:self> components can only exist inside {#if} blocks, {#each} blocks, or slots passed to components',
},
start
);
}
return 'astro:self';
}
if (parser.read(COMPONENT)) return 'astro:component';
if (parser.read(SLOT)) return 'astro:fragment';
if (parser.read(HEAD)) return 'head';
const name = parser.read_until(/(\s|\/|>)/);
if (meta_tags.has(name)) return name;
if (name.startsWith('astro:')) {
const match = fuzzymatch(name.slice(7), valid_meta_tags);
let message = `Valid <astro:...> tag names are ${list(valid_meta_tags)}`;
if (match) message += ` (did you mean '${match}'?)`;
parser.error(
{
code: 'invalid-tag-name',
message,
},
start
);
}
if (!valid_tag_name.test(name)) {
parser.error(
{
code: 'invalid-tag-name',
message: 'Expected valid tag name',
},
start
);
}
return name;
}
function read_attribute(parser: Parser, unique_names: Set<string>) {
const start = parser.index;
function check_unique(name: string) {
if (unique_names.has(name)) {
parser.error(
{
code: 'duplicate-attribute',
message: 'Attributes need to be unique',
},
start
);
}
unique_names.add(name);
}
if (parser.eat('{')) {
parser.allow_whitespace();
if (parser.eat('...')) {
const expression = read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);
return {
start,
end: parser.index,
type: 'Spread',
expression,
};
} else {
const value_start = parser.index;
const name = parser.read_identifier();
parser.allow_whitespace();
parser.eat('}', true);
check_unique(name);
return {
start,
end: parser.index,
type: 'Attribute',
name,
value: [
{
start: value_start,
end: value_start + name.length,
type: 'AttributeShorthand',
expression: {
start: value_start,
end: value_start + name.length,
type: 'Identifier',
name,
},
},
],
};
}
}
const name = parser.read_until(/[\s=\/>"']/);
if (!name) return null;
let end = parser.index;
parser.allow_whitespace();
let value: any[] | true = true;
if (parser.eat('=')) {
parser.allow_whitespace();
value = read_attribute_value(parser);
end = parser.index;
} else if (parser.match_regex(/["']/)) {
parser.error(
{
code: 'unexpected-token',
message: 'Expected =',
},
parser.index
);
}
check_unique(name);
return {
start,
end,
type: 'Attribute',
name,
value,
};
}
function read_attribute_value(parser: Parser) {
const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null;
const regex = quote_mark === "'" ? /'/ : quote_mark === '"' ? /"/ : /(\/>|[\s"'=<>`])/;
const value = read_sequence(parser, () => !!parser.match_regex(regex));
if (quote_mark) parser.index += 1;
return value;
}
export function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
let current_chunk: Text = {
start: parser.index,
end: null,
type: 'Text',
raw: '',
data: null,
};
function flush() {
if (current_chunk.raw) {
current_chunk.data = decode_character_references(current_chunk.raw);
current_chunk.end = parser.index;
chunks.push(current_chunk);
}
}
const chunks: TemplateNode[] = [];
while (parser.index < parser.template.length) {
const index = parser.index;
if (done()) {
flush();
return chunks;
} else if (parser.eat('{')) {
flush();
parser.allow_whitespace();
const expression = read_expression(parser);
parser.allow_whitespace();
parser.eat('}', true);
chunks.push({
start: index,
end: parser.index,
type: 'MustacheTag',
expression,
});
current_chunk = {
start: parser.index,
end: null,
type: 'Text',
raw: '',
data: null,
};
} else {
current_chunk.raw += parser.template[parser.index++];
}
}
parser.error({
code: 'unexpected-eof',
message: 'Unexpected end of input',
});
}

View file

@ -1,32 +0,0 @@
// @ts-nocheck
import { decode_character_references } from '../utils/html.js';
import { Parser } from '../index.js';
export default function text(parser: Parser) {
const start = parser.index;
let data = '';
const shouldContinue = () => {
// Special case 'code' content to avoid tripping up on user code
if (parser.current().name === 'code') {
return !parser.match('<') && !parser.match('{');
}
return !parser.match('<') && !parser.match('{') && !parser.match('`');
};
while (parser.index < parser.template.length && shouldContinue()) {
data += parser.template[parser.index++];
}
const node = {
start,
end: parser.index,
type: 'Text',
raw: data,
data: decode_character_references(data),
};
parser.current().children.push(node);
}

View file

@ -1,27 +0,0 @@
// @ts-nocheck
const SQUARE_BRACKET_OPEN = '['.charCodeAt(0);
const SQUARE_BRACKET_CLOSE = ']'.charCodeAt(0);
const CURLY_BRACKET_OPEN = '{'.charCodeAt(0);
const CURLY_BRACKET_CLOSE = '}'.charCodeAt(0);
export function is_bracket_open(code) {
return code === SQUARE_BRACKET_OPEN || code === CURLY_BRACKET_OPEN;
}
export function is_bracket_close(code) {
return code === SQUARE_BRACKET_CLOSE || code === CURLY_BRACKET_CLOSE;
}
export function is_bracket_pair(open, close) {
return (open === SQUARE_BRACKET_OPEN && close === SQUARE_BRACKET_CLOSE) || (open === CURLY_BRACKET_OPEN && close === CURLY_BRACKET_CLOSE);
}
export function get_bracket_close(open) {
if (open === SQUARE_BRACKET_OPEN) {
return SQUARE_BRACKET_CLOSE;
}
if (open === CURLY_BRACKET_OPEN) {
return CURLY_BRACKET_CLOSE;
}
}

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
export const FEATURE_CUSTOM_ELEMENT = 1 << 0;

View file

@ -1,112 +0,0 @@
// @ts-nocheck
import entities from './entities.js';
const windows_1252 = [
8364, 129, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338, 141, 381, 143, 144, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732, 8482, 353, 8250, 339, 157, 382, 376,
];
const entity_pattern = new RegExp(`&(#?(?:x[\\w\\d]+|\\d+|${Object.keys(entities).join('|')}))(?:;|\\b)`, 'g');
export function decode_character_references(html: string) {
return html.replace(entity_pattern, (match, entity) => {
let code;
// Handle named entities
if (entity[0] !== '#') {
code = entities[entity];
} else if (entity[1] === 'x') {
code = parseInt(entity.substring(2), 16);
} else {
code = parseInt(entity.substring(1), 10);
}
if (!code) {
return match;
}
return String.fromCodePoint(validate_code(code));
});
}
const NUL = 0;
// some code points are verboten. If we were inserting HTML, the browser would replace the illegal
// code points with alternatives in some cases - since we're bypassing that mechanism, we need
// to replace them ourselves
//
// Source: http://en.wikipedia.org/wiki/Character_encodings_in_HTML#Illegal_characters
function validate_code(code: number) {
// line feed becomes generic whitespace
if (code === 10) {
return 32;
}
// ASCII range. (Why someone would use HTML entities for ASCII characters I don't know, but...)
if (code < 128) {
return code;
}
// code points 128-159 are dealt with leniently by browsers, but they're incorrect. We need
// to correct the mistake or we'll end up with missing € signs and so on
if (code <= 159) {
return windows_1252[code - 128];
}
// basic multilingual plane
if (code < 55296) {
return code;
}
// UTF-16 surrogate halves
if (code <= 57343) {
return NUL;
}
// rest of the basic multilingual plane
if (code <= 65535) {
return code;
}
// supplementary multilingual plane 0x10000 - 0x1ffff
if (code >= 65536 && code <= 131071) {
return code;
}
// supplementary ideographic plane 0x20000 - 0x2ffff
if (code >= 131072 && code <= 196607) {
return code;
}
return NUL;
}
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission
const disallowed_contents = new Map([
['li', new Set(['li'])],
['dt', new Set(['dt', 'dd'])],
['dd', new Set(['dt', 'dd'])],
['p', new Set('address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split(' '))],
['rt', new Set(['rt', 'rp'])],
['rp', new Set(['rt', 'rp'])],
['optgroup', new Set(['optgroup'])],
['option', new Set(['option', 'optgroup'])],
['thead', new Set(['tbody', 'tfoot'])],
['tbody', new Set(['tbody', 'tfoot'])],
['tfoot', new Set(['tbody'])],
['tr', new Set(['tr', 'tbody'])],
['td', new Set(['td', 'th', 'tr'])],
['th', new Set(['td', 'th', 'tr'])],
]);
// can this be a child of the parent element, or does it implicitly
// close it, like `<li>one<li>two`?
export function closing_tag_omitted(current: string, next?: string) {
if (disallowed_contents.has(current)) {
if (!next || disallowed_contents.get(current).has(next)) {
return true;
}
}
return false;
}

View file

@ -1,30 +0,0 @@
import { TemplateNode } from '../../interfaces.js';
export function to_string(node: TemplateNode) {
switch (node.type) {
case 'IfBlock':
return '{#if} block';
case 'ThenBlock':
return '{:then} block';
case 'ElseBlock':
return '{:else} block';
case 'PendingBlock':
case 'AwaitBlock':
return '{#await} block';
case 'CatchBlock':
return '{:catch} block';
case 'EachBlock':
return '{#each} block';
case 'RawMustacheTag':
return '{@html} block';
case 'DebugTag':
return '{@debug} block';
case 'Element':
case 'InlineComponent':
case 'Slot':
case 'Title':
return `<${node.name}> tag`;
default:
return node.type;
}
}

View file

@ -1,44 +0,0 @@
// @ts-nocheck
import { locate } from 'locate-character';
import get_code_frame from './get_code_frame.js';
export class CompileError extends Error {
code: string;
end: { line: number; column: number };
filename: string;
frame: string;
start: { line: number; column: number };
constructor({ code, filename, start, end, message }: { code: string; filename: string; start: number; message: string; end?: number }) {
super(message);
this.start = locate(code, start, { offsetLine: 1 });
this.end = locate(code, end || start, { offsetLine: 1 });
this.filename = filename;
this.message = message;
this.frame = get_code_frame(code, this.start.line - 1, this.start.column);
}
toString() {
return `${this.filename}:${this.start.line}:${this.start.column}\n\t${this.message}\n${this.frame}`;
}
}
/** Throw CompileError */
export default function error(
code: string,
message: string,
props: {
name: string;
source: string;
filename: string;
start: number;
end?: number;
}
): never {
const err = new CompileError({ code, message, start: props.start, end: props.end, filename: props.filename });
err.name = props.name;
throw err;
}

View file

@ -1,11 +0,0 @@
// Adapted from https://github.com/acornjs/acorn/blob/6584815dca7440e00de841d1dad152302fdd7ca5/src/tokenize.js
// Reproduced under MIT License https://github.com/acornjs/acorn/blob/master/LICENSE
/** @url https://github.com/acornjs/acorn/blob/6584815dca7440e00de841d1dad152302fdd7ca5/src/tokenize.js */
export default function full_char_code_at(str: string, i: number): number {
const code = str.charCodeAt(i);
if (code <= 0xd7ff || code >= 0xe000) return code;
const next = str.charCodeAt(i + 1);
return (code << 10) + next - 0x35fdc00;
}

View file

@ -1,233 +0,0 @@
// @ts-nocheck
/** Utility for accessing FuzzySet */
export default function fuzzymatch(name: string, names: string[]) {
const set = new FuzzySet(names);
const matches = set.get(name);
return matches && matches[0] && matches[0][0] > 0.7 ? matches[0][1] : null;
}
// adapted from https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js
// BSD Licensed
const GRAM_SIZE_LOWER = 2;
const GRAM_SIZE_UPPER = 3;
/** Return an edit distance from 0 to 1 */
function _distance(str1: string, str2: string) {
if (str1 === null && str2 === null) {
throw 'Trying to compare two null values';
}
if (str1 === null || str2 === null) return 0;
str1 = String(str1);
str2 = String(str2);
const distance = levenshtein(str1, str2);
if (str1.length > str2.length) {
return 1 - distance / str1.length;
} else {
return 1 - distance / str2.length;
}
}
/** @url https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js#L18 */
function levenshtein(str1: string, str2: string) {
const current: number[] = [];
let prev;
let value;
for (let i = 0; i <= str2.length; i++) {
for (let j = 0; j <= str1.length; j++) {
if (i && j) {
if (str1.charAt(j - 1) === str2.charAt(i - 1)) {
value = prev;
} else {
value = Math.min(current[j], current[j - 1], prev) + 1;
}
} else {
value = i + j;
}
prev = current[j];
current[j] = value;
}
}
return current.pop();
}
const non_word_regex = /[^\w, ]+/;
/** @url https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js#L53 */
function iterate_grams(value: string, gram_size = 2) {
const simplified = '-' + value.toLowerCase().replace(non_word_regex, '') + '-';
const len_diff = gram_size - simplified.length;
const results = [];
if (len_diff > 0) {
for (let i = 0; i < len_diff; ++i) {
value += '-';
}
}
for (let i = 0; i < simplified.length - gram_size + 1; ++i) {
results.push(simplified.slice(i, i + gram_size));
}
return results;
}
/** @url https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js#L69 */
function gram_counter(value: string, gram_size = 2) {
// return an object where key=gram, value=number of occurrences
const result = {};
const grams = iterate_grams(value, gram_size);
let i = 0;
for (i; i < grams.length; ++i) {
if (grams[i] in result) {
result[grams[i]] += 1;
} else {
result[grams[i]] = 1;
}
}
return result;
}
/** @url https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js#L158 */
function sort_descending(a, b) {
return b[0] - a[0];
}
class FuzzySet {
exact_set = {};
match_dict = {};
items = {};
constructor(arr: string[]) {
// initialization
for (let i = GRAM_SIZE_LOWER; i < GRAM_SIZE_UPPER + 1; ++i) {
this.items[i] = [];
}
// add all the items to the set
for (let i = 0; i < arr.length; ++i) {
this.add(arr[i]);
}
}
add(value: string) {
const normalized_value = value.toLowerCase();
if (normalized_value in this.exact_set) {
return false;
}
let i = GRAM_SIZE_LOWER;
for (i; i < GRAM_SIZE_UPPER + 1; ++i) {
this._add(value, i);
}
}
_add(value: string, gram_size: number) {
const normalized_value = value.toLowerCase();
const items = this.items[gram_size] || [];
const index = items.length;
items.push(0);
const gram_counts = gram_counter(normalized_value, gram_size);
let sum_of_square_gram_counts = 0;
let gram;
let gram_count;
for (gram in gram_counts) {
gram_count = gram_counts[gram];
sum_of_square_gram_counts += Math.pow(gram_count, 2);
if (gram in this.match_dict) {
this.match_dict[gram].push([index, gram_count]);
} else {
this.match_dict[gram] = [[index, gram_count]];
}
}
const vector_normal = Math.sqrt(sum_of_square_gram_counts);
items[index] = [vector_normal, normalized_value];
this.items[gram_size] = items;
this.exact_set[normalized_value] = value;
}
get(value: string) {
const normalized_value = value.toLowerCase();
const result = this.exact_set[normalized_value];
if (result) {
return [[1, result]];
}
let results = [];
// start with high gram size and if there are no results, go to lower gram sizes
for (let gram_size = GRAM_SIZE_UPPER; gram_size >= GRAM_SIZE_LOWER; --gram_size) {
results = this.__get(value, gram_size);
if (results) {
return results;
}
}
return null;
}
__get(value: string, gram_size: number) {
const normalized_value = value.toLowerCase();
const matches = {};
const gram_counts = gram_counter(normalized_value, gram_size);
const items = this.items[gram_size];
let sum_of_square_gram_counts = 0;
let gram;
let gram_count;
let i;
let index;
let other_gram_count;
for (gram in gram_counts) {
gram_count = gram_counts[gram];
sum_of_square_gram_counts += Math.pow(gram_count, 2);
if (gram in this.match_dict) {
for (i = 0; i < this.match_dict[gram].length; ++i) {
index = this.match_dict[gram][i][0];
other_gram_count = this.match_dict[gram][i][1];
if (index in matches) {
matches[index] += gram_count * other_gram_count;
} else {
matches[index] = gram_count * other_gram_count;
}
}
}
}
const vector_normal = Math.sqrt(sum_of_square_gram_counts);
let results = [];
let match_score;
// build a results list of [score, str]
for (const match_index in matches) {
match_score = matches[match_index];
results.push([match_score / (vector_normal * items[match_index][0]), items[match_index][1]]);
}
results.sort(sort_descending);
let new_results = [];
const end_index = Math.min(50, results.length);
// truncate somewhat arbitrarily to 50
for (let j = 0; j < end_index; ++j) {
new_results.push([_distance(results[j][1], normalized_value), results[j][1]]);
}
results = new_results;
results.sort(sort_descending);
new_results = [];
for (let j = 0; j < results.length; ++j) {
if (results[j][0] == results[0][0]) {
new_results.push([results[j][0], this.exact_set[results[j][1]]]);
}
}
return new_results;
}
}

View file

@ -1,29 +0,0 @@
/** Die you stupid tabs */
function tabs_to_spaces(str: string) {
return str.replace(/^\t+/, (match) => match.split('\t').join(' '));
}
/** Display syntax error in pretty format in logs */
export default function get_code_frame(source: string, line: number, column: number) {
const lines = source.split('\n');
const frame_start = Math.max(0, line - 2);
const frame_end = Math.min(line + 3, lines.length);
const digits = String(frame_end + 1).length;
return lines
.slice(frame_start, frame_end)
.map((str, i) => {
const isErrorLine = frame_start + i === line;
const line_num = String(i + frame_start + 1).padStart(digits, ' ');
if (isErrorLine) {
const indicator = ' '.repeat(digits + 2 + tabs_to_spaces(str.slice(0, column)).length) + '^';
return `${line_num}: ${tabs_to_spaces(str)}\n${indicator}`;
}
return `${line_num}: ${tabs_to_spaces(str)}`;
})
.join('\n');
}

View file

@ -1,5 +0,0 @@
/** Linked list */
export function link<T extends { next?: T; prev?: T }>(next: T, prev: T) {
prev.next = next;
if (next) next.prev = prev;
}

View file

@ -1,5 +0,0 @@
/** Display an array of strings in a human-readable format */
export default function list(items: string[], conjunction = 'or') {
if (items.length === 1) return items[0];
return `${items.slice(0, -1).join(', ')} ${conjunction} ${items[items.length - 1]}`;
}

View file

@ -1,142 +0,0 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import full_char_code_at from './full_char_code_at.js';
export const globals = new Set([
'alert',
'Array',
'Boolean',
'clearInterval',
'clearTimeout',
'confirm',
'console',
'Date',
'decodeURI',
'decodeURIComponent',
'document',
'Element',
'encodeURI',
'encodeURIComponent',
'Error',
'EvalError',
'Event',
'EventSource',
'fetch',
'global',
'globalThis',
'history',
'Infinity',
'InternalError',
'Intl',
'isFinite',
'isNaN',
'JSON',
'localStorage',
'location',
'Map',
'Math',
'NaN',
'navigator',
'Number',
'Node',
'Object',
'parseFloat',
'parseInt',
'process',
'Promise',
'prompt',
'RangeError',
'ReferenceError',
'RegExp',
'sessionStorage',
'Set',
'setInterval',
'setTimeout',
'String',
'SyntaxError',
'TypeError',
'undefined',
'URIError',
'URL',
'window',
]);
export const reserved = new Set([
'arguments',
'await',
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'enum',
'eval',
'export',
'extends',
'false',
'finally',
'for',
'function',
'if',
'implements',
'import',
'in',
'instanceof',
'interface',
'let',
'new',
'null',
'package',
'private',
'protected',
'public',
'return',
'static',
'super',
'switch',
'this',
'throw',
'true',
'try',
'typeof',
'var',
'void',
'while',
'with',
'yield',
]);
const void_element_names = /^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/;
/** Is this a void HTML element? */
export function is_void(name: string) {
return void_element_names.test(name) || name.toLowerCase() === '!doctype';
}
/** Is this a valid HTML element? */
export function is_valid(str: string): boolean {
let i = 0;
while (i < str.length) {
const code = full_char_code_at(str, i);
if (!(i === 0 ? isIdentifierStart : isIdentifierChar)(code, true)) return false;
i += code <= 0xffff ? 1 : 2;
}
return true;
}
/** Utility to normalize HTML */
export function sanitize(name: string) {
return name
.replace(/[^a-zA-Z0-9_]+/g, '_')
.replace(/^_/, '')
.replace(/_$/, '')
.replace(/^[0-9]/, '_$&');
}

View file

@ -1,13 +0,0 @@
// The `foreign` namespace covers all DOM implementations that aren't HTML5.
// It opts out of HTML5-specific a11y checks and case-insensitive attribute names.
export const foreign = 'https://svelte.dev/docs#svelte_options';
export const html = 'http://www.w3.org/1999/xhtml';
export const mathml = 'http://www.w3.org/1998/Math/MathML';
export const svg = 'http://www.w3.org/2000/svg';
export const xlink = 'http://www.w3.org/1999/xlink';
export const xml = 'http://www.w3.org/XML/1998/namespace';
export const xmlns = 'http://www.w3.org/2000/xmlns';
export const valid_namespaces = ['foreign', 'html', 'mathml', 'svg', 'xlink', 'xml', 'xmlns', foreign, html, mathml, svg, xlink, xml, xmlns];
export const namespaces: Record<string, string> = { foreign, html, mathml, svg, xlink, xml, xmlns };

View file

@ -1,35 +0,0 @@
// @ts-nocheck
/** Compare two TemplateNodes to determine if they are equivalent */
export function nodes_match(a, b) {
if (!!a !== !!b) return false;
if (Array.isArray(a) !== Array.isArray(b)) return false;
if (a && typeof a === 'object') {
if (Array.isArray(a)) {
if (a.length !== b.length) return false;
return a.every((child, i) => nodes_match(child, b[i]));
}
const a_keys = Object.keys(a).sort();
const b_keys = Object.keys(b).sort();
if (a_keys.length !== b_keys.length) return false;
let i = a_keys.length;
while (i--) {
const key = a_keys[i];
if (b_keys[i] !== key) return false;
if (key === 'start' || key === 'end') continue;
if (!nodes_match(a[key], b[key])) {
return false;
}
}
return true;
}
return a === b;
}

View file

@ -1,3 +0,0 @@
export const whitespace = /[ \t\r\n]/;
export const dimensions = /^(?:offset|client)(?:Width|Height)$/;

View file

@ -1,9 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"outDir": "./dist"
}
}

View file

@ -112,7 +112,6 @@
"zod": "^3.14.2"
},
"devDependencies": {
"@astrojs/parser": "^0.22.2",
"@babel/types": "^7.17.0",
"@types/babel__core": "^7.1.19",
"@types/babel__traverse": "^7.14.2",

View file

@ -1,6 +1,4 @@
import type { CompileError } from '@astrojs/parser';
import { bold, cyan, dim, red, grey, underline, yellow, reset } from 'kleur/colors';
import { bold, cyan, dim, red, yellow, reset } from 'kleur/colors';
import { performance } from 'perf_hooks';
import { Writable } from 'stream';
import stringWidth from 'string-width';
@ -182,31 +180,6 @@ export function table(opts: LogOptions, columns: number[]) {
};
}
/** Pretty format error for display */
export function parseError(opts: LogOptions, err: CompileError) {
if (!err.frame) {
return error(opts, 'parse-error', err.message || err);
}
let frame = err.frame
// Switch colons for pipes
.replace(/^([0-9]+)(:)/gm, `${bold('$1')}`)
// Make the caret red.
.replace(/(?<=^\s+)(\^)/gm, bold(red(' ^')))
// Add identation
.replace(/^/gm, ' ');
error(
opts,
'parse-error',
`
${underline(bold(grey(`${err.filename || ''}:${err.start.line}:${err.start.column}`)))}
${bold(red(`𝘅 ${err.message}`))}
${frame}
`
);
}
// A default logger for when too lazy to pass LogOptions around.
export const logger = {
info: info.bind(null, defaultLogOptions),

View file

@ -443,7 +443,6 @@ importers:
'@astrojs/compiler': ^0.13.0
'@astrojs/language-server': ^0.8.10
'@astrojs/markdown-remark': ^0.7.0-next.0
'@astrojs/parser': ^0.22.2
'@astrojs/prism': 0.4.0
'@astrojs/webapi': ^0.11.0
'@babel/core': ^7.17.8
@ -563,7 +562,6 @@ importers:
yargs-parser: 21.0.1
zod: 3.14.2
devDependencies:
'@astrojs/parser': link:../astro-parser
'@babel/types': 7.17.0
'@types/babel__core': 7.1.19
'@types/babel__traverse': 7.14.2
@ -587,21 +585,6 @@ importers:
mocha: 9.2.2
sass: 1.49.9
packages/astro-parser:
specifiers:
'@types/node': ^14.18.12
acorn: ^8.7.0
astro-scripts: workspace:*
locate-character: ^2.0.5
magic-string: ^0.25.9
dependencies:
'@types/node': 14.18.12
acorn: 8.7.0
locate-character: 2.0.5
magic-string: 0.25.9
devDependencies:
astro-scripts: link:../../scripts
packages/astro-prism:
specifiers:
prismjs: ^1.27.0
@ -3893,6 +3876,7 @@ packages:
/@types/node/14.18.12:
resolution: {integrity: sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==}
dev: true
/@types/node/16.11.26:
resolution: {integrity: sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==}
@ -4374,6 +4358,7 @@ packages:
resolution: {integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/adm-zip/0.5.9:
resolution: {integrity: sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==}
@ -7235,10 +7220,6 @@ packages:
engines: {node: '>=14'}
dev: true
/locate-character/2.0.5:
resolution: {integrity: sha512-n2GmejDXtOPBAZdIiEFy5dJ5N38xBCXLNOtw2WpB9kGh6pnrEuKlwYI+Tkpofc4wDtVXHtoAOJaMRlYG/oYaxg==}
dev: false
/locate-path/5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}