2
0
mirror of https://github.com/tenrok/BBob.git synced 2026-06-14 18:42:24 +03:00

feat: typescript support (#185)

* feat: initial typescript support

* feat: typescript support

* feat(plugin-helper): move files to typescript

* chore: update lock files

* feat: preset types

* fix: build

* fix: benchmark

* fix: remove pnpm cache

* fix: bench action

* fix: pnpm recursive install

* fix: nx cache

* fix: lock file

* fix: workflows

* fix: lerna support in pnpm

* fix: pnpm workspace

* fix: remove unused files

* fix: pnpm lock file

* fix: update lerna for support pnpm

* fix: lerna bootstrap

* fix: rollup build

* fix: update nx

* fix: build

* fix: add nx dep target

* fix: remove nx cache

* fix: workflow run on push only for master

* fix: test workflow run on push only for master

* fix: remove parallel for gen types

* fix: benchmark

* fix: benchmark imports

* fix: pnpm

* fix: types errors and pnpm

* fix: types

* fix: types

* refactor: parser

* fix(parser): tests

* fix: preset tests

* fix: react types

* fix: react type declarations

* fix: pnpm lock file

* fix: react preset types

* fix: lock file

* fix: vue2 types

* feat: dev container support

* fix: types

* fix: types

* refactor: rewrite pkg-task, add nx gen-types deps, fix react/render.ts

* refactor: types

* fix: types

* fix: rename gen-types to types

* fix: nx build order

* fix: nx reset

* fix: define nx deps explicit

* fix: build

* fix: nx

* fix: nx order build

* fix: nx deps

* fix: bbob cli tests

* fix: tests

* fix: cli tests and import

* fix: test cover

* fix: cli cover
This commit is contained in:
Nikolay Kost
2024-04-23 21:11:14 +02:00
committed by GitHub
parent 05246b2aea
commit 8797f7f363
149 changed files with 6102 additions and 3670 deletions
@@ -1,77 +0,0 @@
import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from './char';
import {
getNodeLength, appendToNode, attrsToString, attrValue, getUniqAttr,
} from './helpers';
const getTagAttrs = (tag, params) => {
const uniqAattr = getUniqAttr(params);
if (uniqAattr) {
const tagAttr = attrValue(tag, uniqAattr);
const attrs = { ...params };
delete attrs[uniqAattr];
const attrsStr = attrsToString(attrs);
return `${tagAttr}${attrsStr}`;
}
return `${tag}${attrsToString(params)}`;
};
class TagNode {
constructor(tag, attrs, content) {
this.tag = tag;
this.attrs = attrs;
this.content = Array.isArray(content) ? content : [content];
}
attr(name, value) {
if (typeof value !== 'undefined') {
this.attrs[name] = value;
}
return this.attrs[name];
}
append(value) {
return appendToNode(this, value);
}
get length() {
return getNodeLength(this);
}
toTagStart({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {
const tagAttrs = getTagAttrs(this.tag, this.attrs);
return `${openTag}${tagAttrs}${closeTag}`;
}
toTagEnd({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {
return `${openTag}${SLASH}${this.tag}${closeTag}`;
}
toTagNode() {
return new TagNode(this.tag.toLowerCase(), this.attrs, this.content);
}
toString({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {
const isEmpty = this.content.length === 0;
const content = this.content.reduce((r, node) => r + node.toString({ openTag, closeTag }), '');
const tagStart = this.toTagStart({ openTag, closeTag });
if (isEmpty) {
return tagStart;
}
return `${tagStart}${content}${this.toTagEnd({ openTag, closeTag })}`;
}
}
TagNode.create = (tag, attrs = {}, content = []) => new TagNode(tag, attrs, content);
TagNode.isOf = (node, type) => (node.tag === type);
export { TagNode };
export default TagNode;
+115
View File
@@ -0,0 +1,115 @@
import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from './char';
import {
getUniqAttr,
getNodeLength,
appendToNode,
attrsToString,
attrValue,
isTagNode,
} from './helpers';
import type { NodeContent, TagNodeObject, TagNodeTree } from "./types";
const getTagAttrs = <AttrValue>(tag: string, params: Record<string, AttrValue>) => {
const uniqAttr = getUniqAttr(params);
if (uniqAttr) {
const tagAttr = attrValue(tag, uniqAttr);
const attrs = { ...params };
delete attrs[String(uniqAttr)];
const attrsStr = attrsToString(attrs);
return `${tagAttr}${attrsStr}`;
}
return `${tag}${attrsToString(params)}`;
};
const renderContent = (content: TagNodeTree, openTag: string, closeTag: string) => {
const toString = (node: NodeContent) => {
if (isTagNode(node)) {
return node.toString({ openTag, closeTag })
}
return String(node)
}
if (Array.isArray(content)) {
return content.reduce<string>((r, node) => {
if (node !== null) {
return r + toString(node)
}
return r
}, '')
}
if (content) {
return toString(content)
}
return null
}
export class TagNode implements TagNodeObject {
public readonly tag: string
public attrs: Record<string, unknown>
public content: TagNodeTree
constructor(tag: string, attrs: Record<string, unknown>, content: TagNodeTree) {
this.tag = tag;
this.attrs = attrs;
this.content = content
}
attr(name: string, value?: unknown) {
if (typeof value !== 'undefined') {
this.attrs[name] = value;
}
return this.attrs[name];
}
append(value: string) {
return appendToNode(this, value);
}
get length(): number {
return getNodeLength(this);
}
toTagStart({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {
const tagAttrs = getTagAttrs(this.tag, this.attrs);
return `${openTag}${tagAttrs}${closeTag}`;
}
toTagEnd({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}) {
return `${openTag}${SLASH}${this.tag}${closeTag}`;
}
toTagNode() {
return new TagNode(this.tag.toLowerCase(), this.attrs, this.content);
}
toString({ openTag = OPEN_BRAKET, closeTag = CLOSE_BRAKET } = {}): string {
const content = this.content ? renderContent(this.content, openTag, closeTag) : ''
const tagStart = this.toTagStart({ openTag, closeTag });
if (this.content === null || Array.isArray(this.content) && this.content.length === 0) {
return tagStart;
}
return `${tagStart}${content}${this.toTagEnd({ openTag, closeTag })}`;
}
static create(tag: string, attrs: Record<string, unknown> = {}, content: TagNodeTree = []) {
return new TagNode(tag, attrs, content)
}
static isOf(node: TagNode, type: string) {
return (node.tag === type)
}
}
-100
View File
@@ -1,100 +0,0 @@
import { N } from './char';
const isTagNode = (el) => typeof el === 'object' && !!el.tag;
const isStringNode = (el) => typeof el === 'string';
const isEOL = (el) => el === N;
const keysReduce = (obj, reduce, def) => Object.keys(obj).reduce(reduce, def);
const getNodeLength = (node) => {
if (isTagNode(node)) {
return node.content.reduce((count, contentNode) => count + getNodeLength(contentNode), 0);
} if (isStringNode(node)) {
return node.length;
}
return 0;
};
/**
* Appends value to Tag Node
* @param {TagNode} node
* @param value
*/
const appendToNode = (node, value) => {
node.content.push(value);
};
/**
* Replaces " to &qquot;
* @param {String} value
*/
const escapeHTML = (value) => value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
// eslint-disable-next-line no-script-url
.replace(/(javascript|data|vbscript):/gi, '$1%3A');
/**
* Acept name and value and return valid html5 attribute string
* @param {String} name
* @param {String} value
* @return {string}
*/
const attrValue = (name, value) => {
const type = typeof value;
const types = {
boolean: () => (value ? `${name}` : ''),
number: () => `${name}="${value}"`,
string: () => `${name}="${escapeHTML(value)}"`,
object: () => `${name}="${escapeHTML(JSON.stringify(value))}"`,
};
return types[type] ? types[type]() : '';
};
/**
* Transforms attrs to html params string
* @param values
*/
const attrsToString = (values) => {
// To avoid some malformed attributes
if (values == null) {
return '';
}
return keysReduce(
values,
(arr, key) => [...arr, attrValue(key, values[key])],
[''],
).join(' ');
};
/**
* Gets value from
* @example
* getUniqAttr({ 'foo': true, 'bar': bar' }) => 'bar'
* @param attrs
* @returns {string}
*/
const getUniqAttr = (attrs) => keysReduce(
attrs,
(res, key) => (attrs[key] === key ? attrs[key] : null),
null,
);
export {
attrsToString,
attrValue,
appendToNode,
escapeHTML,
getNodeLength,
getUniqAttr,
isTagNode,
isStringNode,
isEOL,
};
+125
View File
@@ -0,0 +1,125 @@
import { N } from './char';
import type { TagNode } from "./TagNode";
import type { NodeContent, StringNode } from "./types";
function isTagNode(el: unknown): el is TagNode {
return typeof el === 'object' && el !== null && 'tag' in el;
}
function isStringNode(el: unknown): el is StringNode {
return typeof el === 'string';
}
// check string is end of line
function isEOL(el: string) {
return el === N
}
function keysReduce<Res, Def extends Res, T extends Record<string, unknown>>(obj: T, reduce: (acc: Def, key: keyof T) => Res, def: Def): Res {
const keys = Object.keys(obj)
return keys.reduce((acc, key) => reduce(acc, key), def)
}
function getNodeLength(node: NodeContent): number {
if (isTagNode(node) && Array.isArray(node.content)) {
return node.content.reduce<number>((count, contentNode) => {
return count + getNodeLength(contentNode)
}, 0);
}
if (isStringNode(node)) {
return String(node).length;
}
return 0;
}
function appendToNode(node: TagNode, value: NodeContent) {
if (Array.isArray(node.content)) {
node.content.push(value);
}
}
/**
* Replaces " to &qquot;
* @param {string} value
*/
function escapeAttrValue(value: string) {
return value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
// eslint-disable-next-line no-script-url
.replace(/(javascript|data|vbscript):/gi, '$1%3A');
}
/**
* @deprecated use escapeAttrValue
*/
const escapeHTML = escapeAttrValue
/**
* Accept name and value and return valid html5 attribute string
*/
function attrValue<AttrValue = unknown>(name: string, value: AttrValue) {
// in case of performance
switch (typeof value) {
case 'boolean':
return value ? `${name}` : ''
case 'number':
return `${name}="${value}"`
case 'string':
return `${name}="${escapeAttrValue(value as string)}"`
case 'object':
return `${name}="${escapeAttrValue(JSON.stringify(value))}"`
default:
return ''
}
}
/**
* Transforms attrs to html params string
* @example
* attrsToString({ 'foo': true, 'bar': bar' }) => 'foo="true" bar="bar"'
*/
function attrsToString<AttrValue = unknown>(values: Record<string, AttrValue> | null) {
// To avoid some malformed attributes
if (values == null) {
return '';
}
return keysReduce(
values,
(arr, key) => [...arr, attrValue(key, values[key])],
[''],
).join(' ');
}
/**
* Gets value from
* @example
* getUniqAttr({ 'foo': true, 'bar': bar' }) => 'bar'
*/
function getUniqAttr<Value>(attrs: Record<string, Value>) {
return keysReduce(
attrs,
(res, key) => (attrs[key] === key ? attrs[key] : null),
null,
)
}
export {
attrsToString,
attrValue,
appendToNode,
escapeHTML,
escapeAttrValue,
getNodeLength,
getUniqAttr,
isTagNode,
isStringNode,
isEOL,
};
-3
View File
@@ -1,3 +0,0 @@
export * from './helpers';
export * from './char';
export { TagNode } from './TagNode';
+4
View File
@@ -0,0 +1,4 @@
export * from "./helpers";
export * from "./char";
export * from "./TagNode";
export * from "./types";
+13
View File
@@ -0,0 +1,13 @@
export type StringNode = string | number
export interface TagNodeObject {
readonly tag: string
attrs: Record<string, unknown>
content: TagNodeTree
}
export type NodeContent = TagNodeObject | StringNode | null
export type PartialNodeContent = Partial<TagNodeObject> | StringNode | null
export type TagNodeTree = NodeContent | NodeContent[] | null