2
0
mirror of https://github.com/tenrok/BBob.git synced 2026-06-20 20:00:33 +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
+3
View File
@@ -2,3 +2,6 @@ coverage
dist
lib
es
types
test/*.d.ts
test/*.map
+1 -1
View File
@@ -1,4 +1,4 @@
package-lock.json
pnpm-lock.yaml
coverage
src
!dist
+25 -11
View File
@@ -20,13 +20,24 @@
"core"
],
"dependencies": {
"@bbob/parser": "workspace:*"
"@bbob/parser": "*",
"@bbob/plugin-helper": "*"
},
"main": "lib/index.js",
"module": "es/index.js",
"jsnext:main": "es/index.js",
"browser": "dist/index.js",
"browserName": "BbobCore",
"types": "types/index.d.ts",
"exports": {
".": {
"types": "./types/index.d.ts",
"import": "./es/index.js",
"require": "./lib/index.js",
"browser": "./dist/index.min.js",
"umd": "./dist/index.min.js"
}
},
"homepage": "https://github.com/JiLiZART/bbob",
"author": "Nikolay Kostyurin <jilizart@gmail.com>",
"license": "MIT",
@@ -38,19 +49,22 @@
"url": "git://github.com/JiLiZART/bbob.git"
},
"scripts": {
"build:commonjs": "../../scripts/pkg-task build-commonjs",
"build:es": "../../scripts/pkg-task build-es",
"build:umd": "../../scripts/pkg-task build-umd",
"build": "npm run build:commonjs && npm run build:es && npm run build:umd",
"test": "../../scripts/pkg-task test",
"cover": "../../scripts/pkg-task cover",
"lint": "../../scripts/pkg-task lint",
"size": "../../scripts/pkg-task size",
"bundlesize": "../../scripts/pkg-task bundlesize"
"build:commonjs": "pkg-task",
"build:es": "pkg-task",
"build:umd": "pkg-task",
"build": "pkg-task",
"test": "pkg-task",
"cover": "pkg-task",
"lint": "pkg-task",
"size": "pkg-task",
"bundlesize": "pkg-task",
"types": "pkg-task",
"prepublishOnly": "npm run build"
},
"size-limit": [
{
"path": "lib/index.js"
"path": "./dist/index.min.js",
"limit": "4.5 KB"
}
],
"bundlesize": [
+3
View File
@@ -8,3 +8,6 @@ dependencies:
'@bbob/parser':
specifier: workspace:*
version: link:../bbob-parser
'@bbob/plugin-helper':
specifier: workspace:*
version: link:../bbob-plugin-helper
+10
View File
@@ -0,0 +1,10 @@
let C1 = 'C1'
let C2 = 'C2'
if (process.env.NODE_ENV !== 'production') {
C1 = '"parser" is not a function, please pass to "process(input, { parser })" right function'
C2 = '"render" function not defined, please pass to "process(input, { render })"'
}
export { C1, C2 }
-62
View File
@@ -1,62 +0,0 @@
import { parse } from '@bbob/parser';
import { iterate, match } from './utils';
function walk(cb) {
return iterate(this, cb);
}
export default function bbob(plugs) {
const plugins = typeof plugs === 'function' ? [plugs] : plugs || [];
let options = {
skipParse: false,
};
return {
process(input, opts) {
options = opts || {};
const parseFn = options.parser || parse;
const renderFn = options.render;
const data = options.data || null;
if (typeof parseFn !== 'function') {
throw new Error('"parser" is not a function, please pass to "process(input, { parser })" right function');
}
let tree = options.skipParse
? input || []
: parseFn(input, options);
// raw tree before modification with plugins
const raw = tree;
tree.messages = [];
tree.options = options;
tree.walk = walk;
tree.match = match;
plugins.forEach((plugin) => {
tree = plugin(tree, {
parse: parseFn,
render: renderFn,
iterate,
match,
data,
}) || tree;
});
return {
get html() {
if (typeof renderFn !== 'function') {
throw new Error('"render" function not defined, please pass to "process(input, { render })"');
}
return renderFn(tree, tree.options);
},
tree,
raw,
messages: tree.messages,
};
},
};
}
+76
View File
@@ -0,0 +1,76 @@
import { parse } from '@bbob/parser';
import { iterate, match } from './utils';
import { C1, C2 } from './errors'
import type { IterateCallback } from './utils';
import type { NodeContent, PartialNodeContent } from "@bbob/plugin-helper";
import type { BBobCore, BBobCoreOptions, BBobCoreTagNodeTree, BBobPlugins } from "./types";
export * from './types'
export function createTree<Options extends BBobCoreOptions = BBobCoreOptions>(tree: NodeContent[], options: Options) {
const extendedTree = tree as BBobCoreTagNodeTree
extendedTree.messages = [...(extendedTree.messages || [])]
extendedTree.options = {...options, ...extendedTree.options}
extendedTree.walk = function walkNodes(cb: IterateCallback<NodeContent>) {
return iterate(this, cb);
}
extendedTree.match = function matchNodes(expr: PartialNodeContent | PartialNodeContent[], cb: IterateCallback<NodeContent>) {
return match(this, expr, cb)
}
return extendedTree
}
export default function bbob<InputValue = string | NodeContent[], Options extends BBobCoreOptions = BBobCoreOptions>(
plugs?: BBobPlugins
): BBobCore<InputValue, Options> {
const plugins = typeof plugs === 'function' ? [plugs] : plugs || [];
const mockRender = () => ""
return {
process(input, opts) {
const options = opts || { skipParse: false, parser: parse, render: mockRender, data: null } as BBobCoreOptions
const parseFn = options.parser || parse;
const renderFn = options.render;
const data = options.data || null;
if (typeof parseFn !== 'function') {
throw new Error(C1);
}
// raw tree before modification with plugins
const raw = options.skipParse && Array.isArray(input) ? input : parseFn(input as string, options);
let tree = options.skipParse && Array.isArray(input) ? createTree((input || []) as NodeContent[], options) : createTree(raw, options)
for (let idx = 0; idx < plugins.length; idx++) {
const plugin = plugins[idx]
if (typeof plugin === 'function' && renderFn) {
const newTree = plugin(tree, {
parse: parseFn,
render: renderFn,
iterate,
data,
})
tree = createTree(newTree || tree, options)
}
}
return {
get html() {
if (typeof renderFn !== 'function') {
throw new Error(C2);
}
return renderFn(tree, tree.options);
},
tree,
raw,
messages: tree.messages,
};
},
};
}
+57
View File
@@ -0,0 +1,57 @@
import type { ParseOptions, TagNode } from "@bbob/parser";
import type {
NodeContent,
PartialNodeContent,
TagNodeTree,
} from "@bbob/plugin-helper";
import type { IterateCallback, iterate } from "./utils";
export interface BBobCoreOptions<
Data = unknown | null,
Options extends ParseOptions = ParseOptions
> extends ParseOptions {
skipParse?: boolean;
parser?: (source: string, options?: Options) => TagNode[];
render?: (ast: TagNodeTree, options?: Options) => string;
data?: Data;
}
export interface BbobPluginOptions<
Options extends ParseOptions = ParseOptions
> {
parse: BBobCoreOptions["parser"];
render: (ast: TagNodeTree, options?: Options) => string;
iterate: typeof iterate;
data: unknown | null;
}
export interface BBobPluginFunction {
(tree: BBobCoreTagNodeTree, options: BbobPluginOptions): BBobCoreTagNodeTree;
}
export interface BBobCore<
InputValue = string | TagNode[],
Options extends BBobCoreOptions = BBobCoreOptions
> {
process(
input: InputValue,
opts?: Options
): {
readonly html: string;
tree: BBobCoreTagNodeTree;
raw: TagNode[] | string;
messages: unknown[];
};
}
export interface BBobCoreTagNodeTree extends Array<NodeContent> {
messages: unknown[];
options: BBobCoreOptions;
walk: (cb: IterateCallback<NodeContent>) => BBobCoreTagNodeTree;
match: (
expression: PartialNodeContent | PartialNodeContent[],
cb: IterateCallback<NodeContent>
) => BBobCoreTagNodeTree;
}
export type BBobPlugins = BBobPluginFunction | BBobPluginFunction[];
-60
View File
@@ -1,60 +0,0 @@
/* eslint-disable no-plusplus */
const isObj = (value) => (typeof value === 'object');
const isBool = (value) => (typeof value === 'boolean');
export function iterate(t, cb) {
const tree = t;
if (Array.isArray(tree)) {
for (let idx = 0; idx < tree.length; idx++) {
tree[idx] = iterate(cb(tree[idx]), cb);
}
} else if (tree && isObj(tree) && tree.content) {
iterate(tree.content, cb);
}
return tree;
}
export function same(expected, actual) {
if (typeof expected !== typeof actual) {
return false;
}
if (!isObj(expected) || expected === null) {
return expected === actual;
}
if (Array.isArray(expected)) {
return expected.every((exp) => [].some.call(actual, (act) => same(exp, act)));
}
return Object.keys(expected).every((key) => {
const ao = actual[key];
const eo = expected[key];
if (isObj(eo) && eo !== null && ao !== null) {
return same(eo, ao);
}
if (isBool(eo)) {
return eo !== (ao === null);
}
return ao === eo;
});
}
export function match(expression, cb) {
return Array.isArray(expression)
? iterate(this, (node) => {
for (let idx = 0; idx < expression.length; idx++) {
if (same(expression[idx], node)) {
return cb(node);
}
}
return node;
})
: iterate(this, (node) => (same(expression, node) ? cb(node) : node));
}
+72
View File
@@ -0,0 +1,72 @@
/* eslint-disable no-plusplus */
const isObj = (value: unknown): value is Record<string, unknown> => (typeof value === 'object' && value !== null);
const isBool = (value: unknown): value is boolean => (typeof value === 'boolean');
export type IterateCallback<Content> = (node: Content) => Content
export function iterate<Content, Iterable = ArrayLike<Content> | Content>(t: Iterable, cb: IterateCallback<Content>): Iterable {
const tree = t;
if (Array.isArray(tree)) {
for (let idx = 0; idx < tree.length; idx++) {
tree[idx] = iterate(cb(tree[idx]), cb);
}
} else if (isObj(tree) && 'content' in tree) {
iterate(tree.content, cb);
}
return tree;
}
export function same(expected: unknown, actual: unknown): boolean {
if (typeof expected !== typeof actual) {
return false;
}
if (!isObj(expected) || expected === null) {
return expected === actual;
}
if (Array.isArray(expected)) {
return expected.every((exp) => [].some.call(actual, (act) => same(exp, act)));
}
if (isObj(expected) && isObj(actual)) {
return Object.keys(expected).every((key) => {
const ao = actual[key];
const eo = expected[key];
if (isObj(eo) && isObj(ao)) {
return same(eo, ao);
}
if (isBool(eo)) {
return eo !== (ao === null);
}
return ao === eo;
});
}
return false
}
export function match<Content, Iterable = ArrayLike<Content>>(
t: Iterable,
expression: Content | ArrayLike<Content>,
cb: IterateCallback<Content>
) {
if (Array.isArray(expression)) {
return iterate<Content, Iterable>(t, (node) => {
for (let idx = 0; idx < expression.length; idx++) {
if (same(expression[idx], node)) {
return cb(node);
}
}
return node;
})
}
return iterate<Content, Iterable>(t, (node) => (same(expression, node) ? cb(node) : node));
}
@@ -1,9 +1,10 @@
import { TagNode } from '@bbob/parser'
import core from '../src'
import core, { BBobPluginFunction, BBobPlugins } from '../src'
import { isTagNode } from "@bbob/plugin-helper";
const stringify = val => JSON.stringify(val);
const stringify = (val: unknown) => JSON.stringify(val);
const process = (plugins, input) => core(plugins).process(input, { render: stringify });
const process = (plugins: BBobPlugins, input: string) => core(plugins).process(input, { render: stringify });
describe('@bbob/core', () => {
test('parse bbcode string to ast and html', () => {
@@ -22,17 +23,27 @@ describe('@bbob/core', () => {
});
test('plugin walk api node', () => {
const testPlugin = () => (tree) => tree.walk(node => {
if (node.tag === 'mytag') {
node.attrs = {
pass: 1
};
const testPlugin = () => {
node.content.push('Test');
}
const plugin: BBobPluginFunction = (tree) => tree.walk(node => {
if (isTagNode(node)) {
if (node?.tag === 'mytag') {
node.attrs = {
pass: 1
};
return node
});
if (Array.isArray(node.content)) {
node.content.push('Test');
}
}
}
return node
});
return plugin
}
const res = process([testPlugin()], '[mytag size="15px"]Large Text[/mytag]');
const ast = res.tree;
@@ -56,13 +67,18 @@ describe('@bbob/core', () => {
});
test('plugin walk api string', () => {
const testPlugin = () => (tree) => tree.walk(node => {
if (node === ':)') {
return TagNode.create('test-tag')
}
const testPlugin = () => {
return node
});
const plugin: BBobPluginFunction = (tree) => tree.walk(node => {
if (node === ':)') {
return TagNode.create('test-tag')
}
return node
})
return plugin
};
const res = process([testPlugin()], '[mytag]Large Text :)[/mytag]');
const ast = res.tree;
@@ -89,13 +105,18 @@ describe('@bbob/core', () => {
});
test('plugin match api', () => {
const testPlugin = () => (tree) => tree.match([{ tag: 'mytag1' }, { tag: 'mytag2' }], node => {
if (node.attrs) {
node.attrs['pass'] = 1
}
const testPlugin = () => {
return node
});
const plugin: BBobPluginFunction = (tree) => tree.match([{ tag: 'mytag1' }, { tag: 'mytag2' }], node => {
if (isTagNode(node) && node.attrs) {
node.attrs['pass'] = 1
}
return node
})
return plugin
};
const res = process([testPlugin()], `[mytag1 size="15"]Tag1[/mytag1][mytag2 size="16"]Tag2[/mytag2][mytag3]Tag3[/mytag3]`);
const ast = res.tree;
@@ -1,7 +1,5 @@
import { iterate, match, same } from '../src/utils';
const stringify = val => JSON.stringify(val);
import { isTagNode } from "@bbob/plugin-helper";
describe('@bbob/core utils', () => {
test('iterate', () => {
@@ -14,7 +12,12 @@ describe('@bbob/core utils', () => {
}];
const resultArr = iterate(testArr, node => {
node.pass = 1;
if (typeof node === 'object' && node !== null) {
return {
...node,
pass: 1
}
}
return node;
});
@@ -31,7 +34,7 @@ describe('@bbob/core utils', () => {
}
];
expect(stringify(resultArr)).toEqual(stringify(expected));
expect(resultArr).toEqual(expected);
});
test('match', () => {
const testArr = [
@@ -43,24 +46,25 @@ describe('@bbob/core utils', () => {
{ tag: 'mytag6', six: 1 },
];
testArr.match = match;
const resultArr = testArr.match([{ tag: 'mytag1' }, { tag: 'mytag2' }], node => {
node.pass = 1;
const resultArr = match(testArr, [{ tag: 'mytag1' }, { tag: 'mytag2' }], node => {
if (isTagNode(node)) {
node.attrs = node.attrs || {}
node.attrs.pass = 1
}
return node;
});
const expected = [
{ tag: 'mytag1', one: 1, pass: 1 },
{ tag: 'mytag2', two: 1, pass: 1 },
{ tag: 'mytag1', one: 1, attrs: { pass: 1 } },
{ tag: 'mytag2', two: 1, attrs: { pass: 1 } },
{ tag: 'mytag3', three: 1 },
{ tag: 'mytag4', four: 1 },
{ tag: 'mytag5', five: 1 },
{ tag: 'mytag6', six: 1 },
];
expect(stringify(resultArr)).toEqual(stringify(expected))
expect(resultArr).toEqual(expected)
})
describe('same', () => {
@@ -79,5 +83,8 @@ describe('@bbob/core utils', () => {
test('same object', () => {
expect(same({ foo: true, bar: 'test' }, { foo: true, bar: 'test', ext: true })).toBe(true)
})
test('same string', () => {
expect(same('bar', 'bar')).toBe(true)
})
})
});
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "./types"
},
"include": [
"./src/**/*"
]
}