feat(frontend): scaffold SvelteKit with TS 7.0 (native-preview) and tsgo
This commit is contained in:
+163
@@ -0,0 +1,163 @@
|
||||
/** @import { BaseNode, Command, Visitors } from './types' */
|
||||
|
||||
export const margin = 0;
|
||||
export const newline = 1;
|
||||
export const indent = 2;
|
||||
export const dedent = 3;
|
||||
export const space = 4;
|
||||
|
||||
export class Context {
|
||||
#visitors;
|
||||
#commands;
|
||||
#has_newline = false;
|
||||
|
||||
multiline = false;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Visitors} visitors
|
||||
* @param {Command[]} commands
|
||||
*/
|
||||
constructor(visitors, commands = []) {
|
||||
this.#visitors = visitors;
|
||||
this.#commands = commands;
|
||||
}
|
||||
|
||||
indent() {
|
||||
this.#commands.push(indent);
|
||||
}
|
||||
|
||||
dedent() {
|
||||
this.#commands.push(dedent);
|
||||
}
|
||||
|
||||
margin() {
|
||||
this.#commands.push(margin);
|
||||
}
|
||||
|
||||
newline() {
|
||||
this.#has_newline = true;
|
||||
this.#commands.push(newline);
|
||||
}
|
||||
|
||||
space() {
|
||||
this.#commands.push(space);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Context} context
|
||||
*/
|
||||
append(context) {
|
||||
this.#commands.push(context.#commands);
|
||||
|
||||
if (this.#has_newline || context.multiline) {
|
||||
this.multiline = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} content
|
||||
* @param {BaseNode} [node]
|
||||
*/
|
||||
write(content, node) {
|
||||
if (node?.loc) {
|
||||
this.location(node.loc.start.line, node.loc.start.column);
|
||||
this.#commands.push(content);
|
||||
this.location(node.loc.end.line, node.loc.end.column);
|
||||
} else {
|
||||
this.#commands.push(content);
|
||||
}
|
||||
|
||||
if (this.#has_newline) {
|
||||
this.multiline = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} line
|
||||
* @param {number} column
|
||||
*/
|
||||
location(line, column) {
|
||||
this.#commands.push({ type: 'Location', line, column });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ type: string }} node
|
||||
*/
|
||||
visit(node) {
|
||||
const visitor = this.#visitors[node.type];
|
||||
|
||||
if (!visitor) {
|
||||
let message = `Not implemented: ${node.type}`;
|
||||
|
||||
if (node.type.includes('TS')) {
|
||||
message += ` (consider using 'esrap/languages/ts')`;
|
||||
}
|
||||
|
||||
if (node.type.includes('JSX')) {
|
||||
message += ` (consider using 'esrap/languages/tsx')`;
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
if (this.#visitors._) {
|
||||
// @ts-ignore
|
||||
this.#visitors._(node, this, (node) => visitor(node, this));
|
||||
} else {
|
||||
// @ts-ignore
|
||||
visitor(node, this);
|
||||
}
|
||||
}
|
||||
|
||||
empty() {
|
||||
return !this.#commands.some(has_content);
|
||||
}
|
||||
|
||||
measure() {
|
||||
return measure(this.#commands);
|
||||
}
|
||||
|
||||
new() {
|
||||
return new Context(this.#visitors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Command[]} commands
|
||||
* @param {number} [from]
|
||||
* @param {number} [to]
|
||||
*/
|
||||
function measure(commands, from = 0, to = commands.length) {
|
||||
let total = 0;
|
||||
|
||||
for (let i = from; i < to; i += 1) {
|
||||
const command = commands[i];
|
||||
|
||||
if (typeof command === 'string') {
|
||||
total += command.length;
|
||||
} else if (Array.isArray(command)) {
|
||||
total += measure(command);
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Command} command
|
||||
*/
|
||||
function has_content(command) {
|
||||
if (Array.isArray(command)) {
|
||||
return command.some(has_content);
|
||||
}
|
||||
|
||||
if (typeof command === 'string') {
|
||||
return command.length > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/** @import { BaseNode, Command, Visitors, PrintOptions } from './types' */
|
||||
import { encode } from '@jridgewell/sourcemap-codec';
|
||||
import { Context, dedent, indent, margin, newline, space } from './context.js';
|
||||
|
||||
/** @type {(str: string) => string} str */
|
||||
let btoa = () => {
|
||||
throw new Error('Unsupported environment: `window.btoa` or `Buffer` should be supported.');
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
|
||||
btoa = (str) => window.btoa(unescape(encodeURIComponent(str)));
|
||||
} else if (typeof Buffer === 'function') {
|
||||
btoa = (str) => Buffer.from(str, 'utf-8').toString('base64');
|
||||
}
|
||||
|
||||
class SourceMap {
|
||||
version = 3;
|
||||
|
||||
/** @type {string[]} */
|
||||
names = [];
|
||||
|
||||
/**
|
||||
* @param {[number, number, number, number][][]} mappings
|
||||
* @param {PrintOptions} opts
|
||||
*/
|
||||
constructor(mappings, opts) {
|
||||
this.sources = [opts.sourceMapSource || null];
|
||||
this.sourcesContent = [opts.sourceMapContent || null];
|
||||
this.mappings = opts.sourceMapEncodeMappings === false ? mappings : encode(mappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON representation suitable for saving as a `*.map` file
|
||||
*/
|
||||
toString() {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a base64-encoded JSON representation suitable for inlining at the bottom of a file with `//# sourceMappingURL={...}`
|
||||
*/
|
||||
toUrl() {
|
||||
return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {BaseNode} [T=BaseNode]
|
||||
* @param {T} node
|
||||
* @param {Visitors<T>} visitors
|
||||
* @param {PrintOptions} opts
|
||||
* @returns {{ code: string, map: any }} // TODO
|
||||
*/
|
||||
export function print(node, visitors, opts = {}) {
|
||||
/** @type {Command[]} */
|
||||
const commands = [];
|
||||
|
||||
// @ts-expect-error some nonsense I don't understand
|
||||
const context = new Context(visitors, commands);
|
||||
|
||||
context.visit(node);
|
||||
|
||||
/** @typedef {[number, number, number, number]} Segment */
|
||||
|
||||
let code = '';
|
||||
let current_column = 0;
|
||||
|
||||
/** @type {Segment[][]} */
|
||||
let mappings = [];
|
||||
|
||||
/** @type {Segment[]} */
|
||||
let current_line = [];
|
||||
|
||||
/** @param {string} str */
|
||||
function append(str) {
|
||||
code += str;
|
||||
|
||||
for (let i = 0; i < str.length; i += 1) {
|
||||
if (str[i] === '\n') {
|
||||
mappings.push(current_line);
|
||||
current_line = [];
|
||||
current_column = 0;
|
||||
} else {
|
||||
current_column += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let current_newline = '\n';
|
||||
const indent_str = opts.indent ?? '\t';
|
||||
|
||||
let needs_newline = false;
|
||||
let needs_margin = false;
|
||||
let needs_space = false;
|
||||
|
||||
/** @param {Command} command */
|
||||
function run(command) {
|
||||
if (Array.isArray(command)) {
|
||||
for (let i = 0; i < command.length; i += 1) {
|
||||
run(command[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof command === 'number') {
|
||||
if (command === newline) {
|
||||
needs_newline = true;
|
||||
} else if (command === margin) {
|
||||
needs_margin = true;
|
||||
} else if (command === space) {
|
||||
needs_space = true;
|
||||
} else if (command === indent) {
|
||||
current_newline += indent_str;
|
||||
} else if (command === dedent) {
|
||||
current_newline = current_newline.slice(0, -indent_str.length);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (needs_newline) {
|
||||
append(needs_margin ? '\n' + current_newline : current_newline);
|
||||
} else if (needs_space) {
|
||||
append(' ');
|
||||
}
|
||||
|
||||
needs_margin = needs_newline = needs_space = false;
|
||||
|
||||
if (typeof command === 'string') {
|
||||
append(command);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.type === 'Location') {
|
||||
current_line.push([
|
||||
current_column,
|
||||
0, // source index is always zero
|
||||
command.line - 1,
|
||||
command.column
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < commands.length; i += 1) {
|
||||
run(commands[i]);
|
||||
}
|
||||
|
||||
mappings.push(current_line);
|
||||
|
||||
/** @type {SourceMap} */
|
||||
let map;
|
||||
|
||||
return {
|
||||
code,
|
||||
// create sourcemap lazily in case we don't need it
|
||||
get map() {
|
||||
return (map ??= new SourceMap(mappings, opts));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// it sucks that we have to export the class rather than just
|
||||
// re-exporting it via public.d.ts, but otherwise TypeScript
|
||||
// gets confused about private fields because it is really dumb!
|
||||
export { Context };
|
||||
+2255
File diff suppressed because it is too large
Load Diff
+7
@@ -0,0 +1,7 @@
|
||||
import type { Visitors, BaseNode } from '../../types';
|
||||
import type { TSOptions, BaseComment, Comment } from '../types';
|
||||
export type { BaseComment, Comment };
|
||||
export type Node = BaseNode;
|
||||
export default function ts(options?: TSOptions): Visitors<BaseNode>;
|
||||
// was Record<TSESTree.Expression['type'] | 'Super' | 'RestElement', number>
|
||||
export declare const EXPRESSIONS_PRECEDENCE: Record<string, number>;
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/** @import { TSESTree } from '@typescript-eslint/types' */
|
||||
/** @import { Visitors } from '../../types.js' */
|
||||
/** @import { TSOptions } from '../types.js' */
|
||||
import ts from '../ts/index.js';
|
||||
|
||||
/**
|
||||
* @param {TSOptions} [options]
|
||||
* @returns {Visitors<TSESTree.Node>}
|
||||
*/
|
||||
export default (options) => ({
|
||||
...ts(options),
|
||||
|
||||
JSXElement(node, context) {
|
||||
context.visit(node.openingElement);
|
||||
|
||||
if (node.children.length > 0) {
|
||||
context.indent();
|
||||
}
|
||||
|
||||
for (const child of node.children) {
|
||||
context.visit(child);
|
||||
}
|
||||
|
||||
if (node.children.length > 0) {
|
||||
context.dedent();
|
||||
}
|
||||
|
||||
if (node.closingElement) {
|
||||
context.visit(node.closingElement);
|
||||
}
|
||||
},
|
||||
|
||||
JSXOpeningElement(node, context) {
|
||||
context.write('<');
|
||||
|
||||
context.visit(node.name);
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
context.write(' ');
|
||||
context.visit(attribute);
|
||||
}
|
||||
|
||||
if (node.selfClosing) {
|
||||
context.write(' /');
|
||||
}
|
||||
|
||||
context.write('>');
|
||||
},
|
||||
|
||||
JSXClosingElement(node, context) {
|
||||
context.write('</');
|
||||
|
||||
context.visit(node.name);
|
||||
|
||||
context.write('>');
|
||||
},
|
||||
|
||||
JSXNamespacedName(node, context) {
|
||||
context.visit(node.namespace);
|
||||
context.write(':');
|
||||
context.visit(node.name);
|
||||
},
|
||||
|
||||
JSXIdentifier(node, context) {
|
||||
context.write(node.name, node);
|
||||
},
|
||||
|
||||
JSXMemberExpression(node, context) {
|
||||
context.visit(node.object);
|
||||
context.write('.');
|
||||
context.visit(node.property);
|
||||
},
|
||||
|
||||
JSXText(node, context) {
|
||||
context.write(node.value, node);
|
||||
},
|
||||
|
||||
JSXAttribute(node, context) {
|
||||
context.visit(node.name);
|
||||
if (node.value) {
|
||||
context.write('=');
|
||||
context.visit(node.value);
|
||||
}
|
||||
},
|
||||
|
||||
JSXEmptyExpression(node, context) {},
|
||||
|
||||
JSXFragment(node, context) {
|
||||
context.visit(node.openingFragment);
|
||||
|
||||
if (node.children.length > 0) {
|
||||
context.indent();
|
||||
}
|
||||
|
||||
for (const child of node.children) {
|
||||
context.visit(child);
|
||||
}
|
||||
|
||||
if (node.children.length > 0) {
|
||||
context.dedent();
|
||||
}
|
||||
|
||||
context.visit(node.closingFragment);
|
||||
},
|
||||
|
||||
JSXOpeningFragment(node, context) {
|
||||
context.write('<>');
|
||||
},
|
||||
|
||||
JSXClosingFragment(node, context) {
|
||||
context.write('</>');
|
||||
},
|
||||
|
||||
JSXExpressionContainer(node, context) {
|
||||
context.write('{');
|
||||
|
||||
context.visit(node.expression);
|
||||
|
||||
context.write('}');
|
||||
},
|
||||
|
||||
JSXSpreadChild(node, context) {
|
||||
context.write('{...');
|
||||
|
||||
context.visit(node.expression);
|
||||
|
||||
context.write('}');
|
||||
},
|
||||
|
||||
JSXSpreadAttribute(node, context) {
|
||||
context.write('{...');
|
||||
|
||||
context.visit(node.argument);
|
||||
|
||||
context.write('}');
|
||||
}
|
||||
});
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import type { Visitors, BaseNode } from '../../types';
|
||||
import type { TSOptions, BaseComment, Comment } from '../types';
|
||||
export type { BaseComment, Comment };
|
||||
export type Node = BaseNode;
|
||||
export default function tsx(options?: TSOptions): Visitors<BaseNode>;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import type { BaseNode } from '../types';
|
||||
|
||||
export type TSOptions = {
|
||||
quotes?: 'double' | 'single';
|
||||
comments?: Comment[];
|
||||
getLeadingComments?: (node: BaseNode) => BaseComment[] | undefined;
|
||||
getTrailingComments?: (node: BaseNode) => BaseComment[] | undefined;
|
||||
};
|
||||
|
||||
interface Position {
|
||||
line: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
// this exists in TSESTree but because of the inanity around enums
|
||||
// it's easier to do this ourselves
|
||||
export interface BaseComment {
|
||||
type: 'Line' | 'Block';
|
||||
value: string;
|
||||
start?: number;
|
||||
end?: number;
|
||||
}
|
||||
|
||||
export interface Comment extends BaseComment {
|
||||
loc: {
|
||||
start: Position;
|
||||
end: Position;
|
||||
};
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export type { PrintOptions, Visitors } from './types';
|
||||
export * from './index';
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import type { Context } from 'esrap';
|
||||
|
||||
export type BaseNode = {
|
||||
type: string;
|
||||
loc?: null | {
|
||||
start: { line: number; column: number };
|
||||
end: { line: number; column: number };
|
||||
};
|
||||
};
|
||||
|
||||
type NodeOf<T extends string, X> = X extends { type: T } ? X : never;
|
||||
|
||||
type SpecialisedVisitors<T extends BaseNode> = {
|
||||
[K in T['type']]?: Visitor<NodeOf<K, T>>;
|
||||
};
|
||||
|
||||
export type Visitor<T> = (node: T, context: Context) => void;
|
||||
|
||||
export type Visitors<T extends BaseNode = BaseNode> = T['type'] extends '_'
|
||||
? never
|
||||
: SpecialisedVisitors<T> & { _?: (node: T, context: Context, visit: (node: T) => void) => void };
|
||||
|
||||
export { Context };
|
||||
|
||||
type TSExpressionWithTypeArguments = {
|
||||
type: 'TSExpressionWithTypeArguments';
|
||||
expression: any;
|
||||
};
|
||||
|
||||
export interface Location {
|
||||
type: 'Location';
|
||||
line: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
export interface IndentChange {
|
||||
type: 'IndentChange';
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export type Command = string | number | Location | Command[];
|
||||
|
||||
export interface PrintOptions {
|
||||
sourceMapSource?: string;
|
||||
sourceMapContent?: string;
|
||||
sourceMapEncodeMappings?: boolean; // default true
|
||||
indent?: string; // default tab
|
||||
}
|
||||
Reference in New Issue
Block a user