diff --git a/.changeset/five-meals-sing.md b/.changeset/five-meals-sing.md new file mode 100644 index 0000000..effcfc1 --- /dev/null +++ b/.changeset/five-meals-sing.md @@ -0,0 +1,27 @@ +--- +"@bbob/plugin-helper": minor +"@bbob/preset-html5": minor +"@bbob/preset-react": minor +"@bbob/preset-vue": minor +"@bbob/parser": minor +"@bbob/preset": minor +"@bbob/react": minor +"@bbob/core": minor +"@bbob/html": minor +"@bbob/vue2": minor +"@bbob/vue3": minor +"@bbob/cli": minor +--- + +** BREAKING CHANGE ** + +`TagNode.create` method now by default pass `null` to content instead of empty array `[]` + +```js + // new behavior + TagNode.create('img').toString() // -> [img] + // old behavior + TagNode.create('img', {}, []).toString() // -> [img][/img] +``` + +Migrate all calls of `TagNode.create('test-tag')` to `TagNode.create('test-tag', {}, []) diff --git a/benchmark/benchmark.js b/benchmark/benchmark.js new file mode 100644 index 0000000..222a8e7 --- /dev/null +++ b/benchmark/benchmark.js @@ -0,0 +1,18 @@ +/* eslint-disable global-require */ + +const parser = require('@bbob/parser'); +const stub = require('./test/stub'); + +const passes = 100; +const results = new Array(passes); +const lexer = parser.createLexer; + +// eslint-disable-next-line no-plusplus +for (let i = 0; i < passes; i++) { + results[i] = parser.parse(stub, { + onlyAllowTags: ['ch'], + createTokenizer: lexer, + }); +} + +console.log(results.length); diff --git a/benchmark/package.json b/benchmark/package.json index 2bfdcff..c6d1f0b 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -6,7 +6,8 @@ "test": "test" }, "scripts": { - "start": "node index.js" + "start": "node index.js", + "cpupro": "node --require cpupro benchmark.js" }, "author": { "name": "Nikolay Kostyurin ", @@ -18,6 +19,7 @@ "benchmark": "2.1.4", "picocolors": "1.0.0", "xbbcode-parser": "0.1.2", + "cpupro": "*", "ya-bbcode": "1.0.12" } } diff --git a/lerna.json b/lerna.json index 385a864..f468f2c 100644 --- a/lerna.json +++ b/lerna.json @@ -16,7 +16,8 @@ "packages/bbob-preset-vue", "packages/bbob-react", "packages/bbob-vue2", - "packages/bbob-vue3" + "packages/bbob-vue3", + "packages/bbob-types" ], "publishConfig": { "access": "public", diff --git a/nx.json b/nx.json index 9952b4e..5a88e62 100644 --- a/nx.json +++ b/nx.json @@ -5,27 +5,25 @@ "^types" ], "outputs": ["{projectRoot}/types"], - "cache": true }, "build": { "dependsOn": [ "^types", - "^build:commonjs", "^build:es", + "^build:commonjs", "^build:umd" ], "outputs": ["{projectRoot}/lib", "{projectRoot}/es", "{projectRoot}/dist"], - "cache": true }, "test": { "dependsOn": [ - "^build", + "build", "^test" ] }, "cover": { "dependsOn": [ - "^build", + "build", "^cover" ], "outputs": ["{projectRoot}/coverage"] @@ -37,26 +35,23 @@ }, "build:commonjs": { "dependsOn": [ - "^build:es", + "build:es", "^build:commonjs" ], "outputs": ["{projectRoot}/lib"], - "cache": true }, "build:es": { "dependsOn": [ "^build:es" ], "outputs": ["{projectRoot}/es"], - "cache": true }, "build:umd": { "dependsOn": [ - "^build:es", + "build:es", "^build:umd" ], "outputs": ["{projectRoot}/dist"], - "cache": true } }, "$schema": "./node_modules/nx/schemas/nx-schema.json", diff --git a/packages/bbob-cli/package.json b/packages/bbob-cli/package.json index 92bd924..b0f5def 100644 --- a/packages/bbob-cli/package.json +++ b/packages/bbob-cli/package.json @@ -47,6 +47,7 @@ "build:umd": "pkg-task", "build": "pkg-task", "test": "pkg-task", + "cover": "pkg-task", "lint": "pkg-task", "size": "pkg-task", "bundlesize": "pkg-task", diff --git a/packages/bbob-core/package.json b/packages/bbob-core/package.json index 8af558b..3de9fab 100644 --- a/packages/bbob-core/package.json +++ b/packages/bbob-core/package.json @@ -21,7 +21,8 @@ ], "dependencies": { "@bbob/parser": "*", - "@bbob/plugin-helper": "*" + "@bbob/plugin-helper": "*", + "@bbob/types": "*" }, "main": "lib/index.js", "module": "es/index.js", diff --git a/packages/bbob-core/src/index.ts b/packages/bbob-core/src/index.ts index 7dadc05..40d4150 100644 --- a/packages/bbob-core/src/index.ts +++ b/packages/bbob-core/src/index.ts @@ -1,13 +1,17 @@ +import type { + BBobCore, + BBobCoreOptions, + BBobCoreTagNodeTree, + BBobPlugins, + IterateCallback, + NodeContent, + PartialNodeContent +} from "@bbob/types"; + 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(tree: NodeContent[], options: Options) { const extendedTree = tree as BBobCoreTagNodeTree diff --git a/packages/bbob-core/src/types.ts b/packages/bbob-core/src/types.ts deleted file mode 100644 index 8cfed97..0000000 --- a/packages/bbob-core/src/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -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 { - messages: unknown[]; - options: BBobCoreOptions; - walk: (cb: IterateCallback) => BBobCoreTagNodeTree; - match: ( - expression: PartialNodeContent | PartialNodeContent[], - cb: IterateCallback - ) => BBobCoreTagNodeTree; -} - -export type BBobPlugins = BBobPluginFunction | BBobPluginFunction[]; diff --git a/packages/bbob-core/src/utils.ts b/packages/bbob-core/src/utils.ts index d271e78..281ffdf 100644 --- a/packages/bbob-core/src/utils.ts +++ b/packages/bbob-core/src/utils.ts @@ -1,9 +1,9 @@ /* eslint-disable no-plusplus */ +import { IterateCallback } from "@bbob/types"; + const isObj = (value: unknown): value is Record => (typeof value === 'object' && value !== null); const isBool = (value: unknown): value is boolean => (typeof value === 'boolean'); -export type IterateCallback = (node: Content) => Content - export function iterate | Content>(t: Iterable, cb: IterateCallback): Iterable { const tree = t; diff --git a/packages/bbob-core/test/index.test.ts b/packages/bbob-core/test/index.test.ts index f873227..a7923ad 100644 --- a/packages/bbob-core/test/index.test.ts +++ b/packages/bbob-core/test/index.test.ts @@ -71,7 +71,7 @@ describe('@bbob/core', () => { const plugin: BBobPluginFunction = (tree) => tree.walk(node => { if (node === ':)') { - return TagNode.create('test-tag') + return TagNode.create('test-tag', {}, []) } return node diff --git a/packages/bbob-html/package.json b/packages/bbob-html/package.json index 391ba43..bca29e6 100644 --- a/packages/bbob-html/package.json +++ b/packages/bbob-html/package.json @@ -10,7 +10,8 @@ ], "dependencies": { "@bbob/core": "*", - "@bbob/plugin-helper": "*" + "@bbob/plugin-helper": "*", + "@bbob/types": "*" }, "main": "lib/index.js", "module": "es/index.js", diff --git a/packages/bbob-html/src/index.ts b/packages/bbob-html/src/index.ts index 1248e75..9122cce 100644 --- a/packages/bbob-html/src/index.ts +++ b/packages/bbob-html/src/index.ts @@ -1,14 +1,15 @@ -import core, { BBobCoreOptions, BBobPlugins } from '@bbob/core'; -import { attrsToString, isTagNode, TagNode, TagNodeTree } from '@bbob/plugin-helper'; +import core from '@bbob/core'; +import { attrsToString, isTagNode, TagNode } from '@bbob/plugin-helper'; +import type { BBobCoreOptions, BBobPlugins, TagNodeTree } from '@bbob/types'; const SELFCLOSE_END_TAG = '/>'; const CLOSE_START_TAG = ''; -export type BBobHTMLOptions = { +interface BBobHTMLOptions extends BBobCoreOptions { stripTags?: boolean -} & BBobCoreOptions +} function renderNode(node?: TagNodeTree, options?: BBobHTMLOptions): string { const { stripTags = false } = options || {} @@ -42,8 +43,8 @@ function renderNode(node?: TagNodeTree, options?: BBobHTMLOptions): string { return ''; } -export function render(nodes: TagNodeTree, options?: BBobHTMLOptions): string { - if (Array.isArray(nodes)) { +export function render(nodes?: TagNodeTree, options?: BBobHTMLOptions): string { + if (nodes && Array.isArray(nodes)) { return nodes.reduce((r, node) => r + renderNode(node, options), '') } diff --git a/packages/bbob-parser/package.json b/packages/bbob-parser/package.json index 7866023..5e53ff8 100644 --- a/packages/bbob-parser/package.json +++ b/packages/bbob-parser/package.json @@ -20,7 +20,8 @@ "types" ], "dependencies": { - "@bbob/plugin-helper": "*" + "@bbob/plugin-helper": "*", + "@bbob/types": "*" }, "main": "lib/index.js", "module": "es/index.js", diff --git a/packages/bbob-parser/src/Token.ts b/packages/bbob-parser/src/Token.ts index 43f3b02..da1008d 100644 --- a/packages/bbob-parser/src/Token.ts +++ b/packages/bbob-parser/src/Token.ts @@ -3,6 +3,7 @@ import { CLOSE_BRAKET, SLASH, } from '@bbob/plugin-helper'; +import type { Token as TokenInterface } from "@bbob/types"; // type, value, line, row, @@ -87,11 +88,11 @@ const tokenToText = (token: Token) => { * @export * @class Token */ -class Token { - private t: number // type - private v: string // value - private l: number // line - private r: number // row +class Token implements TokenInterface { + readonly t: number // type + readonly v: string // value + readonly l: number // line + readonly r: number // row constructor(type?: number, value?: TokenValue, row: number = 0, col: number = 0) { this[TOKEN_LINE_ID] = row; diff --git a/packages/bbob-parser/src/index.ts b/packages/bbob-parser/src/index.ts index 82e60e8..2c7f357 100644 --- a/packages/bbob-parser/src/index.ts +++ b/packages/bbob-parser/src/index.ts @@ -2,4 +2,3 @@ export { TagNode } from '@bbob/plugin-helper'; export { default } from './parse'; export * from './parse'; export * from './lexer' -export * from './types' diff --git a/packages/bbob-parser/src/lexer.ts b/packages/bbob-parser/src/lexer.ts index 546555c..8363473 100644 --- a/packages/bbob-parser/src/lexer.ts +++ b/packages/bbob-parser/src/lexer.ts @@ -10,12 +10,12 @@ import { EQ, N, } from '@bbob/plugin-helper'; +import type { LexerOptions, LexerTokenizer } from "@bbob/types"; import { Token, TYPE_ATTR_NAME, TYPE_ATTR_VALUE, TYPE_NEW_LINE, TYPE_SPACE, TYPE_TAG, TYPE_WORD, } from './Token'; import { CharGrabber, createCharGrabber, trimChar, unquote } from './utils'; -import type { LexerOptions, LexerTokenizer } from "./types"; // for cases const EM = '!'; @@ -24,15 +24,24 @@ export function createTokenOfType(type: number, value: string, r = 0, cl = 0) { return new Token(type, value, r, cl) } +const STATE_WORD = 0; +const STATE_TAG = 1; +const STATE_TAG_ATTRS = 2; + +const TAG_STATE_NAME = 0; +const TAG_STATE_ATTR = 1; +const TAG_STATE_VALUE = 2; + +const WHITESPACES = [SPACE, TAB]; +const SPECIAL_CHARS = [EQ, SPACE, TAB]; + +const isWhiteSpace = (char: string) => (WHITESPACES.indexOf(char) >= 0); +const isEscapeChar = (char: string) => char === BACKSLASH; +const isSpecialChar = (char: string) => (SPECIAL_CHARS.indexOf(char) >= 0); +const isNewLine = (char: string) => char === N; +const unq = (val: string) => unquote(trimChar(val, QUOTEMARK)); + export function createLexer(buffer: string, options: LexerOptions = {}): LexerTokenizer { - const STATE_WORD = 0; - const STATE_TAG = 1; - const STATE_TAG_ATTRS = 2; - - const TAG_STATE_NAME = 0; - const TAG_STATE_ATTR = 1; - const TAG_STATE_VALUE = 2; - let row = 0; let col = 0; @@ -47,6 +56,7 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo const contextFreeTags = (options.contextFreeTags || []) .filter(Boolean) .map((tag) => tag.toLowerCase()); + const nestedMap = new Map(); const onToken = options.onToken || (() => { }); @@ -54,22 +64,14 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo const NOT_CHAR_TOKENS = [ openTag, SPACE, TAB, N, ]; - const WHITESPACES = [SPACE, TAB]; - const SPECIAL_CHARS = [EQ, SPACE, TAB]; const isCharReserved = (char: string) => (RESERVED_CHARS.indexOf(char) >= 0); - const isNewLine = (char: string) => char === N; - const isWhiteSpace = (char: string) => (WHITESPACES.indexOf(char) >= 0); const isCharToken = (char: string) => (NOT_CHAR_TOKENS.indexOf(char) === -1); - const isSpecialChar = (char: string) => (SPECIAL_CHARS.indexOf(char) >= 0); const isEscapableChar = (char: string) => (char === openTag || char === closeTag || char === BACKSLASH); - const isEscapeChar = (char: string) => char === BACKSLASH; const onSkip = () => { col++; }; - const unq = (val: string) => unquote(trimChar(val, QUOTEMARK)); - const checkContextFreeMode = (name: string, isClosingTag?: boolean) => { if (contextFreeTag !== '' && isClosingTag) { contextFreeTag = ''; @@ -339,8 +341,16 @@ export function createLexer(buffer: string, options: LexerOptions = {}): LexerTo function isTokenNested(token: Token) { const value = openTag + SLASH + token.getValue(); - // potential bottleneck - return buffer.indexOf(value) > -1; + + if (nestedMap.has(value)) { + return !!nestedMap.get(value); + } else { + const status = (buffer.indexOf(value) > -1) + + nestedMap.set(value, status); + + return status; + } } return { diff --git a/packages/bbob-parser/src/parse.ts b/packages/bbob-parser/src/parse.ts index 3aecb6b..e1a0759 100644 --- a/packages/bbob-parser/src/parse.ts +++ b/packages/bbob-parser/src/parse.ts @@ -1,3 +1,5 @@ +import type { NodeContent, TagNodeTree, LexerTokenizer, ParseOptions } from "@bbob/types"; + import { CLOSE_BRAKET, OPEN_BRAKET, @@ -7,26 +9,8 @@ import { import { createLexer } from "./lexer"; -import type { NodeContent, TagNodeTree } from "@bbob/plugin-helper"; -import type { LexerTokenizer, LexerOptions } from "./types"; import type { Token } from "./Token"; -type ParseError = { - tagName: string; - lineNumber: number; - columnNumber: number; -}; - -export interface ParseOptions { - createTokenizer?: (input: string, options?: LexerOptions) => LexerTokenizer; - openTag?: string; - closeTag?: string; - onlyAllowTags?: string[]; - contextFreeTags?: string[]; - enableEscapeTags?: boolean; - onError?: (error: ParseError) => void; -} - class NodeList { private n: Value[]; @@ -201,7 +185,7 @@ function parse(input: string, opts: ParseOptions = {}) { function handleTagStart(token: Token) { flushTagNodes(); - const tagNode = TagNode.create(token.getValue()); + const tagNode = TagNode.create(token.getValue(), {}, []); const isNested = isTokenNested(token); tagNodes.push(tagNode); diff --git a/packages/bbob-parser/src/types.ts b/packages/bbob-parser/src/types.ts deleted file mode 100644 index c2dd1b7..0000000 --- a/packages/bbob-parser/src/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type Token from "./Token"; - -export interface LexerTokenizer { - tokenize: () => Token[]; - isTokenNested?: (token: Token) => boolean; -} - -export type LexerOptions = { - openTag?: string; - closeTag?: string; - onlyAllowTags?: string[]; - enableEscapeTags?: boolean; - contextFreeTags?: string[]; - onToken?: (token?: Token) => void; -}; diff --git a/packages/bbob-parser/src/utils.ts b/packages/bbob-parser/src/utils.ts index 4df3362..d66a56e 100644 --- a/packages/bbob-parser/src/utils.ts +++ b/packages/bbob-parser/src/utils.ts @@ -35,6 +35,10 @@ export class CharGrabber { } getCurr() { + if (typeof this.s[this.c.pos] === 'undefined') { + return '' + } + return this.s[this.c.pos] } @@ -51,7 +55,11 @@ export class CharGrabber { getPrev() { const prevPos = this.c.pos - 1; - return typeof this.s[prevPos] !== 'undefined' ? this.s[prevPos] : null; + if (typeof this.s[prevPos] === 'undefined') { + return null + } + + return this.s[prevPos]; } isLast() { diff --git a/packages/bbob-plugin-helper/package.json b/packages/bbob-plugin-helper/package.json index f4d9464..833e731 100644 --- a/packages/bbob-plugin-helper/package.json +++ b/packages/bbob-plugin-helper/package.json @@ -20,6 +20,9 @@ "browser": "dist/index.js", "browserName": "BbobPluginHelper", "types": "types/index.d.ts", + "dependencies": { + "@bbob/types": "*" + }, "exports": { ".": { "types": "./types/index.d.ts", diff --git a/packages/bbob-plugin-helper/src/TagNode.ts b/packages/bbob-plugin-helper/src/TagNode.ts index 40c1eec..902842d 100644 --- a/packages/bbob-plugin-helper/src/TagNode.ts +++ b/packages/bbob-plugin-helper/src/TagNode.ts @@ -1,3 +1,5 @@ +import type { NodeContent, TagNodeObject, TagNodeTree } from "@bbob/types"; + import { OPEN_BRAKET, CLOSE_BRAKET, SLASH } from './char'; import { getUniqAttr, @@ -8,8 +10,6 @@ import { isTagNode, } from './helpers'; -import type { NodeContent, TagNodeObject, TagNodeTree } from "./types"; - const getTagAttrs = (tag: string, params: Record) => { const uniqAttr = getUniqAttr(params); @@ -105,7 +105,7 @@ export class TagNode implements TagNodeObject { return `${tagStart}${content}${this.toTagEnd({ openTag, closeTag })}`; } - static create(tag: string, attrs: Record = {}, content: TagNodeTree = []) { + static create(tag: string, attrs: Record = {}, content: TagNodeTree = null) { return new TagNode(tag, attrs, content) } diff --git a/packages/bbob-plugin-helper/src/helpers.ts b/packages/bbob-plugin-helper/src/helpers.ts index 4f3859b..f999e6f 100644 --- a/packages/bbob-plugin-helper/src/helpers.ts +++ b/packages/bbob-plugin-helper/src/helpers.ts @@ -1,6 +1,7 @@ +import type { NodeContent, StringNode } from "@bbob/types"; + 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; @@ -15,10 +16,10 @@ function isEOL(el: string) { return el === N } -function keysReduce>(obj: T, reduce: (acc: Def, key: keyof T) => Res, def: Def): Res { +function keysReduce>(obj: T, reduce: (acc: Def, key: keyof T, obj: T) => Res, def: Def): Res { const keys = Object.keys(obj) - return keys.reduce((acc, key) => reduce(acc, key), def) + return keys.reduce((acc, key) => reduce(acc, key, obj), def) } function getNodeLength(node: NodeContent): number { @@ -85,7 +86,7 @@ function attrValue(name: string, value: AttrValue) { * @example * attrsToString({ 'foo': true, 'bar': bar' }) => 'foo="true" bar="bar"' */ -function attrsToString(values: Record | null) { +function attrsToString(values?: Record | null) { // To avoid some malformed attributes if (values == null) { return ''; @@ -93,7 +94,7 @@ function attrsToString(values: Record | return keysReduce( values, - (arr, key) => [...arr, attrValue(key, values[key])], + (arr, key, obj) => [...arr, attrValue(key, obj[key])], [''], ).join(' '); } @@ -103,10 +104,10 @@ function attrsToString(values: Record | * @example * getUniqAttr({ 'foo': true, 'bar': bar' }) => 'bar' */ -function getUniqAttr(attrs: Record) { +function getUniqAttr(attrs?: Record) { return keysReduce( - attrs, - (res, key) => (attrs[key] === key ? attrs[key] : null), + attrs || {}, + (res, key, obj) => (obj[key] === key ? obj[key] : null), null, ) } diff --git a/packages/bbob-plugin-helper/src/index.ts b/packages/bbob-plugin-helper/src/index.ts index 3c93b97..3cfcb82 100644 --- a/packages/bbob-plugin-helper/src/index.ts +++ b/packages/bbob-plugin-helper/src/index.ts @@ -1,4 +1,3 @@ export * from "./helpers"; export * from "./char"; export * from "./TagNode"; -export * from "./types"; diff --git a/packages/bbob-plugin-helper/test/TagNode.test.ts b/packages/bbob-plugin-helper/test/TagNode.test.ts index 9a0977a..0383342 100644 --- a/packages/bbob-plugin-helper/test/TagNode.test.ts +++ b/packages/bbob-plugin-helper/test/TagNode.test.ts @@ -44,6 +44,18 @@ describe('@bbob/plugin-helper/TagNode', () => { expect(newTagNode.content).toEqual(tagNode.content); }); + test('null content', () => { + const tagNode = TagNode.create('img'); + + expect(String(tagNode)).toBe('[img]'); + }); + + test('array content', () => { + const tagNode = TagNode.create('img', {}, []); + + expect(String(tagNode)).toBe('[img]'); + }); + describe('toString', () => { test('tag with content and params', () => { const tagNode = TagNode.create('test', {test: 1}, ['Hello']); diff --git a/packages/bbob-preset-html5/package.json b/packages/bbob-preset-html5/package.json index 58a2938..3215e6e 100644 --- a/packages/bbob-preset-html5/package.json +++ b/packages/bbob-preset-html5/package.json @@ -9,7 +9,8 @@ ], "dependencies": { "@bbob/plugin-helper": "*", - "@bbob/preset": "*" + "@bbob/preset": "*", + "@bbob/types": "*" }, "devDependencies": { "@bbob/core": "*", diff --git a/packages/bbob-preset-html5/src/defaultTags.ts b/packages/bbob-preset-html5/src/defaultTags.ts index b5e4b67..8ef7d02 100644 --- a/packages/bbob-preset-html5/src/defaultTags.ts +++ b/packages/bbob-preset-html5/src/defaultTags.ts @@ -4,18 +4,18 @@ import { isStringNode, isTagNode, TagNode, - TagNodeTree, } from "@bbob/plugin-helper"; -import type { NodeContent, TagNodeObject } from "@bbob/plugin-helper"; -import type { PresetTagsDefinition } from "@bbob/preset"; -import type { BbobPluginOptions } from "@bbob/core"; + +import type { BBobPluginOptions, PresetTagsDefinition, NodeContent, TagNodeTree, TagNodeObject } from "@bbob/types"; const isStartsWith = (node: string, type: string) => node[0] === type; -const getStyleFromAttrs = (attrs: Record) => { - return Object.keys(attrs) +const styleAttrs = (attrs?: Record) => { + const values = attrs || {} + + return Object.keys(values) .reduce((acc, key: "color" | "size") => { - const value = attrs[key]; + const value = values[key]; if (typeof value === "string") { if (key === "color") { @@ -32,53 +32,49 @@ const getStyleFromAttrs = (attrs: Record) => { .join(" "); }; -const asListItems = (content: TagNodeTree): NodeContent[] => { - let listIdx = 0; - const listItems = [] as Array; +export const toListNodes = (content?: TagNodeTree) => { + if (content && Array.isArray(content)) { + return content.reduce((acc, node) => { + const listItem = acc[acc.length - 1]; - const createItemNode = () => TagNode.create("li"); - const ensureListItem = (val: NodeContent) => { - listItems[listIdx] = listItems[listIdx] || val; - }; - const addItem = (val: NodeContent) => { - const listItem = listItems[listIdx]; + // *Entry + if (isStringNode(node) && isStartsWith(String(node), "*")) { + // from '*Entry' to 'Entry' + const content = String(node).slice(1) - if (listItem && isTagNode(listItem) && Array.isArray(listItem.content)) { - listItem.content = listItem.content.concat(val); - } - // else if (Array.isArray(listItem) && Array.isArray(listItems[listIdx])) { - // listItems[listIdx] = listItems[listIdx].concat(val); - // } - }; + acc.push(TagNode.create("li", {}, [content])); - if (Array.isArray(content)) { - content.forEach((el) => { - if (isStringNode(el) && isStartsWith(String(el), "*")) { - if (listItems[listIdx]) { - listIdx++; - } - ensureListItem(createItemNode()); - addItem(String(el).substr(1)); - } else if (isTagNode(el) && TagNode.isOf(el, "*")) { - if (listItems[listIdx]) { - listIdx++; - } - ensureListItem(createItemNode()); - } else if (!isTagNode(listItems[listIdx])) { - listIdx++; - ensureListItem(el); - } else if (listItems[listIdx]) { - addItem(el); - } else { - ensureListItem(el); + return acc } - }); + + // { tag: '*', attrs: {}, content: [] } + if (isTagNode(node) && TagNode.isOf(node, "*")) { + acc.push(TagNode.create("li", {}, [])); + + return acc + } + + if (!isTagNode(listItem)) { + acc.push(node); + return acc + } + + if (listItem && isTagNode(listItem) && Array.isArray(listItem.content)) { + listItem.content = listItem.content.concat(node); + + return acc + } + + acc.push(node); + + return acc + }, []); } - return listItems; + return content }; -const renderUrl = (node: TagNodeObject, render: BbobPluginOptions["render"]) => +const renderUrl = (node: TagNodeObject, render: BBobPluginOptions["render"]) => getUniqAttr(node.attrs) ? getUniqAttr(node.attrs) : render(node.content || []); @@ -86,61 +82,53 @@ const renderUrl = (node: TagNodeObject, render: BbobPluginOptions["render"]) => const toNode = ( tag: string, attrs: Record, - content: TagNodeTree + content?: TagNodeTree ) => TagNode.create(tag, attrs, content); const toStyle = (style: string) => ({ style }); -const defaultTags: PresetTagsDefinition< - | "b" - | "i" - | "u" - | "s" - | "url" - | "img" - | "quote" - | "code" - | "style" - | "list" - | "color" -> = { - b: (node) => toNode("span", toStyle("font-weight: bold;"), node.content), - i: (node) => toNode("span", toStyle("font-style: italic;"), node.content), - u: (node) => - toNode("span", toStyle("text-decoration: underline;"), node.content), - s: (node) => - toNode("span", toStyle("text-decoration: line-through;"), node.content), - url: (node, { render }) => - toNode( - "a", - { - href: renderUrl(node, render), - }, - node.content - ), - img: (node, { render }) => - toNode( - "img", - { - src: render(node.content), - }, - null - ), - quote: (node) => toNode("blockquote", {}, [toNode("p", {}, node.content)]), - code: (node) => toNode("pre", {}, node.content), - style: (node) => - toNode("span", toStyle(getStyleFromAttrs(node.attrs)), node.content), - list: (node) => { - const type = getUniqAttr(node.attrs); +export const defineStyleNode = (tag: string, style: string) => (node: TagNodeObject) => toNode(tag, toStyle(style), node.content) - return toNode( - type ? "ol" : "ul", - type ? { type } : {}, - asListItems(node.content) - ); - }, - color: (node) => - toNode("span", toStyle(`color: ${getUniqAttr(node.attrs)};`), node.content), -}; +export const defaultTags = (function createTags() { + const tags: PresetTagsDefinition = { + b: defineStyleNode("span", "font-weight: bold;"), + i: defineStyleNode("span", "font-style: italic;"), + u: defineStyleNode("span", "text-decoration: underline;"), + s: defineStyleNode("span", "text-decoration: line-through;"), + url: (node, { render }) => + toNode( + "a", + { + href: renderUrl(node, render), + }, + node.content + ), + img: (node, { render }) => + toNode( + "img", + { + src: render(node.content), + }, + null + ), + quote: (node) => toNode("blockquote", {}, [toNode("p", {}, node.content)]), + code: (node) => toNode("pre", {}, node.content), + style: (node) => + toNode("span", toStyle(styleAttrs(node.attrs)), node.content), + list: (node) => { + const type = getUniqAttr(node.attrs); + + return toNode( + type ? "ol" : "ul", + type ? { type } : {}, + toListNodes(node.content) + ); + }, + color: (node) => + toNode("span", toStyle(`color: ${getUniqAttr(node.attrs)};`), node.content), + } + + return tags +})(); export default defaultTags; diff --git a/packages/bbob-preset-react/package.json b/packages/bbob-preset-react/package.json index 8e9415a..a9d46e2 100644 --- a/packages/bbob-preset-react/package.json +++ b/packages/bbob-preset-react/package.json @@ -9,10 +9,7 @@ ], "dependencies": { "@bbob/preset-html5": "*", - "@bbob/preset": "*" - }, - "devDependencies": { - "@bbob/core": "*" + "@bbob/types": "*" }, "main": "lib/index.js", "module": "es/index.js", diff --git a/packages/bbob-preset-react/src/index.ts b/packages/bbob-preset-react/src/index.ts index cb8a4db..e3d545d 100644 --- a/packages/bbob-preset-react/src/index.ts +++ b/packages/bbob-preset-react/src/index.ts @@ -1,5 +1,5 @@ import presetHTML5 from '@bbob/preset-html5'; -import type { PresetTagsDefinition } from '@bbob/preset'; +import type { PresetTagsDefinition } from '@bbob/types'; const tagAttr = (style: Record) => ({ attrs: { @@ -7,7 +7,7 @@ const tagAttr = (style: Record) => ({ }, }); -export default presetHTML5.extend((tags: PresetTagsDefinition<'b' | 'i' | 'u' | 's'>) => ({ +const presetReact = presetHTML5.extend((tags: PresetTagsDefinition<'b' | 'i' | 'u' | 's'>) => ({ ...tags, b: (...args) => ({ @@ -30,3 +30,5 @@ export default presetHTML5.extend((tags: PresetTagsDefinition<'b' | 'i' | 'u' | ...tagAttr({ textDecoration: 'line-through' }), }), })); + +export default presetReact; diff --git a/packages/bbob-preset-vue/package.json b/packages/bbob-preset-vue/package.json index df5b8ec..d424f05 100644 --- a/packages/bbob-preset-vue/package.json +++ b/packages/bbob-preset-vue/package.json @@ -9,10 +9,7 @@ ], "dependencies": { "@bbob/preset-html5": "*", - "@bbob/preset": "*" - }, - "devDependencies": { - "@bbob/core": "*" + "@bbob/types": "*" }, "main": "lib/index.js", "module": "es/index.js", diff --git a/packages/bbob-preset-vue/src/index.ts b/packages/bbob-preset-vue/src/index.ts index 7b02777..aaf0195 100644 --- a/packages/bbob-preset-vue/src/index.ts +++ b/packages/bbob-preset-vue/src/index.ts @@ -1,5 +1,6 @@ import presetHTML5 from '@bbob/preset-html5'; -import type { PresetTagsDefinition } from '@bbob/preset'; + +import type { PresetTagsDefinition } from '@bbob/types'; export const tagAttr = (style: Record) => ({ attrs: { @@ -7,29 +8,35 @@ export const tagAttr = (style: Record) => ({ }, }); -export const createTags = (tags: PresetTagsDefinition<'b' | 'i' | 'u' | 's'>) => ({ - b: (...args) => ({ - ...tags.b(...args), - ...tagAttr({ fontWeight: 'bold' }), - }), +export const createTags = (tags: PresetTagsDefinition) => { + const newTags: PresetTagsDefinition = { + b: (...args) => ({ + ...tags.b?.(...args), + ...tagAttr({ fontWeight: 'bold' }), + }), - i: (...args) => ({ - ...tags.i(...args), - ...tagAttr({ fontStyle: 'italic' }), - }), + i: (...args) => ({ + ...tags.i?.(...args), + ...tagAttr({ fontStyle: 'italic' }), + }), - u: (...args) => ({ - ...tags.u(...args), - ...tagAttr({ textDecoration: 'underline' }), - }), + u: (...args) => ({ + ...tags.u?.(...args), + ...tagAttr({ textDecoration: 'underline' }), + }), - s: (...args) => ({ - ...tags.s(...args), - ...tagAttr({ textDecoration: 'line-through' }), - }), -} as PresetTagsDefinition); + s: (...args) => ({ + ...tags.s?.(...args), + ...tagAttr({ textDecoration: 'line-through' }), + }), + } -export default presetHTML5.extend((tags) => ({ + return newTags +}; + +const presetVue = presetHTML5.extend((tags: PresetTagsDefinition) => ({ ...tags, ...createTags(tags), })); + +export default presetVue; diff --git a/packages/bbob-preset-vue/test/index.test.ts b/packages/bbob-preset-vue/test/index.test.ts index 9c57a3a..a9e75e4 100644 --- a/packages/bbob-preset-vue/test/index.test.ts +++ b/packages/bbob-preset-vue/test/index.test.ts @@ -1,23 +1,28 @@ +import type { PresetTagFunction } from "@bbob/types"; + import preset, { createTags, tagAttr } from '../src' +const tagFactory = (tag: string): PresetTagFunction => jest.fn((...args) => ({ tag })) +const createTag = (tag: string, style: Record) => ({ tag, ...tagAttr(style)}) + describe('@bbob/preset-vue', () => { test('is a function', () => { expect(preset).toBeInstanceOf(Function) }) test('createTags', () => { - const defFn = jest.fn(() => ({})) const defTags = { - b: defFn, - i: defFn, - u: defFn, - s: defFn, + b: tagFactory('b'), + i: tagFactory('i'), + u: tagFactory('u'), + s: tagFactory('s'), } const tags = createTags(defTags) + const args = [{tag: 'test'}] - expect(tags.b()).toEqual(tagAttr({ fontWeight: 'bold' })) - expect(tags.i()).toEqual(tagAttr({ fontStyle: 'italic' })) - expect(tags.u()).toEqual(tagAttr({ textDecoration: 'underline' })) - expect(tags.s()).toEqual(tagAttr({ textDecoration: 'line-through' })) + expect(tags.b?.({tag: 'b'}, ...args)).toEqual(createTag('b',{ fontWeight: 'bold' })) + expect(tags.i?.({tag: 'i'}, ...args)).toEqual(createTag('i',{ fontStyle: 'italic' })) + expect(tags.u?.({tag: 'u'}, ...args)).toEqual(createTag('u',{ textDecoration: 'underline' })) + expect(tags.s?.({tag: 's'}, ...args)).toEqual(createTag('s',{ textDecoration: 'line-through' })) }) }); diff --git a/packages/bbob-preset/package.json b/packages/bbob-preset/package.json index 7b6fc0c..a7643b5 100644 --- a/packages/bbob-preset/package.json +++ b/packages/bbob-preset/package.json @@ -16,6 +16,9 @@ ], "dependencies": { "@bbob/plugin-helper": "*", + "@bbob/types": "*" + }, + "devDependencies": { "@bbob/core": "*" }, "main": "lib/index.js", diff --git a/packages/bbob-preset/src/index.ts b/packages/bbob-preset/src/index.ts index a47d5c4..15a762c 100644 --- a/packages/bbob-preset/src/index.ts +++ b/packages/bbob-preset/src/index.ts @@ -1,3 +1,2 @@ export { default } from "./preset"; export * from "./preset"; -export * from "./types"; diff --git a/packages/bbob-preset/src/preset.ts b/packages/bbob-preset/src/preset.ts index 7e9d3db..0e555e0 100644 --- a/packages/bbob-preset/src/preset.ts +++ b/packages/bbob-preset/src/preset.ts @@ -1,87 +1,63 @@ +import { isTagNode } from "@bbob/plugin-helper"; + import type { BBobCoreTagNodeTree, - BbobPluginOptions, - BBobPluginFunction, -} from "@bbob/core"; -import { isTagNode } from "@bbob/plugin-helper"; -import type { + BBobPluginOptions, PresetExtendCallback, - PresetFactoryOptions, + PresetFactory, PresetOptions, PresetTagsDefinition, -} from "./types"; + ProcessorFunction +} from "@bbob/types"; -function process( - tags: PresetTagsDefinition, +export function process( + tags: Tags, tree: BBobCoreTagNodeTree, - core: BbobPluginOptions, - options: PresetFactoryOptions = {} + core: BBobPluginOptions, + options: Options ) { return tree.walk((node) => { - if (isTagNode(node) && typeof tags[node.tag] === "function") { + if (isTagNode(node)) { const tag = node.tag; const tagCallback = tags[tag]; - return tagCallback(node, core, options); + if (typeof tagCallback === "function") { + return tagCallback(node, core, options); + } } return node; }); } -export type ProcessorFunction = typeof process; - -export type ProcessorReturnType = ReturnType; - -export interface PresetExecutor< - TagName extends string = string, - AttrValue = unknown -> extends BBobPluginFunction { - (tree: BBobCoreTagNodeTree, core?: BbobPluginOptions): ProcessorReturnType; - options: PresetOptions; -} - -export interface PresetFactory< - TagName extends string = string, - AttrValue = unknown, - Names extends string = string -> { - (opts?: PresetOptions): PresetExecutor; - options?: PresetOptions; - extend: ( - cb: PresetExtendCallback - ) => PresetFactory; -} - /** * Create a preset plugin for @bbob/core */ -function createPreset( - defTags: PresetTagsDefinition, - processor: ProcessorFunction = process +function createPreset( + defTags: Tags, + processor: ProcessorFunction = process ) { - const presetFactory: PresetFactory = (opts: PresetOptions = {}) => { + const presetFactory: PresetFactory = (opts?: Options) => { presetFactory.options = Object.assign(presetFactory.options || {}, opts); function presetExecutor( tree: BBobCoreTagNodeTree, - core: BbobPluginOptions + core: BBobPluginOptions ) { - return processor(defTags, tree, core, presetFactory.options); + return processor(defTags, tree, core, presetFactory.options || {}); } - presetExecutor.options = presetFactory.options; + presetExecutor.options = presetFactory.options as Options; return presetExecutor; }; - presetFactory.extend = function presetExtend( - callback: PresetExtendCallback + presetFactory.extend = function presetExtend( + callback: PresetExtendCallback ) { - return createPreset( - callback(defTags, presetFactory.options || {}), - processor - ); + const newTags = callback(defTags, presetFactory.options) + + return createPreset(newTags, processor as unknown as ProcessorFunction); }; return presetFactory; diff --git a/packages/bbob-preset/src/types.ts b/packages/bbob-preset/src/types.ts deleted file mode 100644 index 50e93a3..0000000 --- a/packages/bbob-preset/src/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { TagNodeObject } from "@bbob/plugin-helper"; -import type { BbobPluginOptions } from "@bbob/core"; - -export type PresetFactoryOptions = Record - -export type PresetTagFunction = ( - node: Node, - core: BbobPluginOptions, - options: PresetFactoryOptions -) => Node - -export type PresetTagsDefinition = Record - -export type PresetOptions = Record -export type PresetExtendCallback = (defTags: PresetTagsDefinition, options: PresetOptions) => PresetTagsDefinition diff --git a/packages/bbob-preset/test/index.test.ts b/packages/bbob-preset/test/index.test.ts index 6c72408..8db1f5a 100644 --- a/packages/bbob-preset/test/index.test.ts +++ b/packages/bbob-preset/test/index.test.ts @@ -2,21 +2,22 @@ import { createPreset, PresetTagsDefinition } from '../src'; import { BBobCoreOptions, createTree } from '@bbob/core' describe('@bbob/preset', () => { - const presetFactory = (defTags: PresetTagsDefinition) => { + const presetFactory = (defTags: Tags) => { const processor = jest.fn((tags, tree, core, options) => tags) + const preset = createPreset(defTags, processor) return { - preset: createPreset(defTags, processor), + preset, processor, core: {} as BBobCoreOptions } } test('create', () => { - const defTags = { test: () => 'test' } + const defTags = { test: () => ({ tag: 'test' }) } const options = { foo: 'bar' } const tree = createTree([], {}) - const { preset, processor, core } = presetFactory(defTags); + const { preset, processor } = presetFactory(defTags); expect(preset.extend) .toBeDefined(); @@ -28,8 +29,8 @@ describe('@bbob/preset', () => { expect(processor.mock.calls.length).toBe(1); }); test('extend', () => { - const defTags = { foo: () => 'foo' } - const extendedTags = { bar: () => 'bar' } + const defTags = { foo: () => ({ tag: 'foo'}) } + const extendedTags = { bar: () =>({tag: 'bar'}) } const options = { foo: 'bar' } const tree = createTree([], {}) const { preset, processor, core } = presetFactory(defTags); @@ -47,8 +48,8 @@ describe('@bbob/preset', () => { expect(processor.mock.calls.length).toBe(1); }); test('pass options', () => { - const { preset, processor } = presetFactory({ test: () => 'test' }); - const newPreset = preset.extend((tags, options) => ({ bar: () => 'bar' })); + const { preset } = presetFactory({ test: () => ({tag: 'test'}) }); + const newPreset = preset.extend((tags, options) => ({ bar: () => ({tag: 'bar'}) })); const instance = preset({ foo: 'bar' }); const instance2 = newPreset({ some: 'some' }); diff --git a/packages/bbob-react/package.json b/packages/bbob-react/package.json index cba801d..592184a 100644 --- a/packages/bbob-react/package.json +++ b/packages/bbob-react/package.json @@ -11,7 +11,8 @@ "dependencies": { "@bbob/core": "*", "@bbob/html": "*", - "@bbob/plugin-helper": "*" + "@bbob/plugin-helper": "*", + "@bbob/types": "*" }, "peerDependencies": { "react": "> 15.0" diff --git a/packages/bbob-react/src/Component.ts b/packages/bbob-react/src/Component.ts index 0b504f1..038b26d 100644 --- a/packages/bbob-react/src/Component.ts +++ b/packages/bbob-react/src/Component.ts @@ -1,8 +1,13 @@ import React, { ReactNode } from 'react'; -import type { BBobPlugins, BBobCoreOptions } from '@bbob/core'; +import type { BBobPlugins, BBobCoreOptions } from '@bbob/types'; + import { render } from './render'; -const content = (children: ReactNode, plugins?: BBobPlugins, options?: BBobCoreOptions) => React.Children.map(children, (child) => (typeof child === 'string' ? render(child, plugins, options) : child)); +const content = (children: ReactNode, plugins?: BBobPlugins, options?: BBobCoreOptions) => + React.Children.map(children, + (child) => + (typeof child === 'string' ? render(child, plugins, options) : child) + ); export type BBobReactComponentProps = { children: ReactNode diff --git a/packages/bbob-react/src/render.ts b/packages/bbob-react/src/render.ts index c6691b5..5ee2bcb 100644 --- a/packages/bbob-react/src/render.ts +++ b/packages/bbob-react/src/render.ts @@ -1,20 +1,22 @@ /* eslint-disable no-use-before-define */ import React, { ReactNode } from "react"; import { render as htmlrender } from "@bbob/html"; -import core, { - BBobCoreOptions, - BBobCoreTagNodeTree, - BBobPlugins, -} from "@bbob/core"; +import core from "@bbob/core"; import { isTagNode, isStringNode, isEOL, TagNode, - TagNodeTree, } from "@bbob/plugin-helper"; +import type { + BBobCoreOptions, + BBobCoreTagNodeTree, + BBobPlugins, + TagNodeTree, +} from "@bbob/types"; + const toAST = ( source: string, plugins?: BBobPlugins, @@ -45,8 +47,8 @@ function tagToReactElement(node: TagNode, index: number) { ); } -function renderToReactNodes(nodes: BBobCoreTagNodeTree | TagNodeTree) { - if (Array.isArray(nodes) && nodes.length) { +function renderToReactNodes(nodes?: BBobCoreTagNodeTree | TagNodeTree) { + if (nodes && Array.isArray(nodes) && nodes.length) { return nodes.reduce((arr, node, index) => { if (isTagNode(node)) { arr.push(tagToReactElement(node, index)); diff --git a/packages/bbob-types/.eslintignore b/packages/bbob-types/.eslintignore new file mode 100644 index 0000000..dc87967 --- /dev/null +++ b/packages/bbob-types/.eslintignore @@ -0,0 +1,4 @@ +dist +es +lib +test diff --git a/packages/bbob-types/.gitignore b/packages/bbob-types/.gitignore new file mode 100644 index 0000000..3f22f0b --- /dev/null +++ b/packages/bbob-types/.gitignore @@ -0,0 +1,8 @@ +coverage +dist +lib +es +types +test/*.d.ts +test/*.map +tsconfig.tsbuildinfo diff --git a/packages/bbob-types/.npmignore b/packages/bbob-types/.npmignore new file mode 100644 index 0000000..c104619 --- /dev/null +++ b/packages/bbob-types/.npmignore @@ -0,0 +1,7 @@ +pnpm-lock.yaml +coverage +src +!dist +!lib +!es +*.test.js diff --git a/packages/bbob-types/package.json b/packages/bbob-types/package.json new file mode 100644 index 0000000..1b9934c --- /dev/null +++ b/packages/bbob-types/package.json @@ -0,0 +1,34 @@ +{ + "name": "@bbob/types", + "version": "3.0.2", + "description": "Shared Typescript types of @bbob", + "keywords": [ + "bbcode", + "types" + ], + "files": [ + "dist", + "lib", + "src", + "es", + "types" + ], + "types": "types/index.d.ts", + "module": "types/index.d.ts", + "homepage": "https://github.com/JiLiZART/bbob", + "author": "Nikolay Kostyurin ", + "license": "MIT", + "bugs": { + "url": "https://github.com/JiLiZART/bbob/issues" + }, + "repository": { + "type": "git", + "url": "git://github.com/JiLiZART/bbob.git" + }, + "scripts": { + "types": "pkg-task" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/bbob-types/src/core.ts b/packages/bbob-types/src/core.ts new file mode 100644 index 0000000..b365742 --- /dev/null +++ b/packages/bbob-types/src/core.ts @@ -0,0 +1,54 @@ +import { ParseOptions } from "./parser"; +import { NodeContent, PartialNodeContent, TagNodeObject, TagNodeTree } from "./types"; + +export interface BBobCoreOptions< + Data = unknown | null, + Options extends ParseOptions = ParseOptions +> extends ParseOptions { + skipParse?: boolean; + parser?: (source: string, options?: Options) => TagNodeObject[]; + render?: (ast?: TagNodeTree, options?: Options) => string; + data?: Data; +} + +export type IterateCallback = (node: Content) => Content + +export interface BBobPluginOptions< + Options extends ParseOptions = ParseOptions, +> { + parse: BBobCoreOptions["parser"]; + render: (ast?: TagNodeTree, options?: Options) => string; + iterate: | Content>(t: Iterable, cb: IterateCallback) => Iterable; + data: unknown | null; +} + +export interface BBobPluginFunction { + (tree: BBobCoreTagNodeTree, options: BBobPluginOptions): BBobCoreTagNodeTree; +} + +export interface BBobCore< + InputValue = string | TagNodeObject[], + Options extends BBobCoreOptions = BBobCoreOptions +> { + process( + input: InputValue, + opts?: Options + ): { + readonly html: string; + tree: BBobCoreTagNodeTree; + raw: TagNodeObject[] | string; + messages: unknown[]; + }; +} + +export interface BBobCoreTagNodeTree extends Array { + messages: unknown[]; + options: BBobCoreOptions; + walk: (cb: IterateCallback) => BBobCoreTagNodeTree; + match: ( + expression: PartialNodeContent | PartialNodeContent[], + cb: IterateCallback + ) => BBobCoreTagNodeTree; +} + +export type BBobPlugins = BBobPluginFunction | BBobPluginFunction[]; diff --git a/packages/bbob-types/src/index.ts b/packages/bbob-types/src/index.ts new file mode 100644 index 0000000..966399a --- /dev/null +++ b/packages/bbob-types/src/index.ts @@ -0,0 +1,4 @@ +export * from './types' +export * from './parser' +export * from './core' +export * from './preset' diff --git a/packages/bbob-types/src/parser.ts b/packages/bbob-types/src/parser.ts new file mode 100644 index 0000000..8a61281 --- /dev/null +++ b/packages/bbob-types/src/parser.ts @@ -0,0 +1,44 @@ +import { TagNodeTree } from "./types"; + +export interface ParseError { + tagName: string; + lineNumber: number; + columnNumber: number; +} + +export interface TagNode { + readonly tag: string + attrs?: Record + content?: TagNodeTree +} + +export interface Token { + readonly t: number // type + readonly v: string // value + readonly l: number // line + readonly r: number // row +} + +export interface LexerTokenizer { + tokenize: () => Token[]; + isTokenNested?: (token: Token) => boolean; +} + +export interface LexerOptions { + openTag?: string; + closeTag?: string; + onlyAllowTags?: string[]; + enableEscapeTags?: boolean; + contextFreeTags?: string[]; + onToken?: (token?: Token) => void; +} + +export interface ParseOptions { + createTokenizer?: (input: string, options?: LexerOptions) => LexerTokenizer; + openTag?: string; + closeTag?: string; + onlyAllowTags?: string[]; + contextFreeTags?: string[]; + enableEscapeTags?: boolean; + onError?: (error: ParseError) => void; +} diff --git a/packages/bbob-types/src/preset.ts b/packages/bbob-types/src/preset.ts new file mode 100644 index 0000000..6f9808d --- /dev/null +++ b/packages/bbob-types/src/preset.ts @@ -0,0 +1,46 @@ + +import { BBobCoreTagNodeTree, BBobPluginFunction, BBobPluginOptions } from "./core"; +import { TagNodeObject } from "./types"; + +export type PartialRecord = Partial> + +export type PresetTagsDefinition = Record + +export type PresetOptions = Record + +export type ProcessorFunction = ( + tags: Tags, + tree: BBobCoreTagNodeTree, + core: BBobPluginOptions, + options: Options +) => BBobCoreTagNodeTree + +// export type ProcessorReturnType = ReturnType; + +export interface PresetTagFunction { + ( + node: Node, + data: BBobPluginOptions, + options: Options + ): Node +} + +export interface PresetExtendCallback { + (defTags: Tags, options?: Options): NewTags +} + +export interface PresetExecutor extends BBobPluginFunction { + (tree: BBobCoreTagNodeTree, core?: BBobPluginOptions): BBobCoreTagNodeTree; + options: Options; +} + +export interface PresetFactory< + Tags extends PresetTagsDefinition = PresetTagsDefinition, + RootOptions extends PresetOptions = PresetOptions, +> { + (opts?: Options): PresetExecutor; + options?: RootOptions; + extend: ( + cb: PresetExtendCallback + ) => PresetFactory; +} diff --git a/packages/bbob-plugin-helper/src/types.ts b/packages/bbob-types/src/types.ts similarity index 83% rename from packages/bbob-plugin-helper/src/types.ts rename to packages/bbob-types/src/types.ts index 2ec7573..073579a 100644 --- a/packages/bbob-plugin-helper/src/types.ts +++ b/packages/bbob-types/src/types.ts @@ -2,8 +2,8 @@ export type StringNode = string | number export interface TagNodeObject { readonly tag: string - attrs: Record - content: TagNodeTree + attrs?: Record + content?: TagNodeTree } export type NodeContent = TagNodeObject | StringNode | null diff --git a/packages/bbob-types/tsconfig.json b/packages/bbob-types/tsconfig.json new file mode 100644 index 0000000..385f1a8 --- /dev/null +++ b/packages/bbob-types/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "./types" + }, + "include": [ + "./src/**/*" + ] +} diff --git a/packages/bbob-vue2/package.json b/packages/bbob-vue2/package.json index b1b1643..0abf40b 100644 --- a/packages/bbob-vue2/package.json +++ b/packages/bbob-vue2/package.json @@ -11,7 +11,8 @@ "dependencies": { "@bbob/core": "*", "@bbob/html": "*", - "@bbob/plugin-helper": "*" + "@bbob/plugin-helper": "*", + "@bbob/types": "*" }, "peerDependencies": { "vue": "2.x" diff --git a/packages/bbob-vue2/src/Component.ts b/packages/bbob-vue2/src/Component.ts index 6dfb8df..bf645f6 100644 --- a/packages/bbob-vue2/src/Component.ts +++ b/packages/bbob-vue2/src/Component.ts @@ -1,7 +1,7 @@ import { defineComponent } from 'vue'; +import type { BBobCoreOptions, BBobPlugins } from '@bbob/types'; import { render } from './render'; -import type { BBobCoreOptions, BBobPlugins } from '@bbob/core'; export type BBobVueComponentProps = { container: string diff --git a/packages/bbob-vue2/src/render.ts b/packages/bbob-vue2/src/render.ts index 7a8b386..c5cee0e 100644 --- a/packages/bbob-vue2/src/render.ts +++ b/packages/bbob-vue2/src/render.ts @@ -1,36 +1,36 @@ /* eslint-disable no-use-before-define,import/prefer-default-export */ -import core, { BBobPlugins, BBobCoreOptions } from '@bbob/core'; +import core from '@bbob/core'; import * as html from '@bbob/html'; import { isStringNode, isTagNode } from '@bbob/plugin-helper'; -import type { TagNodeTree, TagNode } from "@bbob/plugin-helper"; +import type { TagNodeTree, TagNode, BBobPlugins, BBobCoreOptions } from '@bbob/types'; import type { CreateElement, VNodeChildrenArrayContents } from 'vue'; import type { StyleValue } from 'vue/types/jsx'; const toAST = (source: string, plugins: BBobPlugins = [], options: BBobCoreOptions = {}) => core(plugins) - .process(source, { - ...options, - render: (input) => html.render(input, { stripTags: true }), - }).tree; + .process(source, { + ...options, + render: (input) => html.render(input, { stripTags: true }), + }).tree; -const isContentEmpty = (content: TagNodeTree) => (!content || Array.isArray(content) && content?.length === 0); +const isContentEmpty = (content?: TagNodeTree) => (!content || Array.isArray(content) && content?.length === 0); function tagToVueNode(createElement: CreateElement, node: TagNode, index: number) { const { class: className, style, ...domProps } = node.attrs || {}; return createElement( - node.tag, - { - key: index, - class: className, - style: style as StyleValue, - domProps, - }, - isContentEmpty(node.content) ? null : renderToVueNodes(createElement, node.content), + node.tag, + { + key: index, + class: className, + style: style as StyleValue, + domProps, + }, + isContentEmpty(node.content) ? null : renderToVueNodes(createElement, node.content), ); } -function renderToVueNodes(createElement: CreateElement, nodes: TagNodeTree) { +function renderToVueNodes(createElement: CreateElement, nodes?: TagNodeTree) { if (Array.isArray(nodes)) { return nodes.reduce((arr, node, index) => { if (isTagNode(node)) { @@ -38,7 +38,7 @@ function renderToVueNodes(createElement: CreateElement, nodes: TagNodeTree) { } else if (isStringNode(node)) { arr.push(String(node)); } - + return arr; }, [] as VNodeChildrenArrayContents); } diff --git a/packages/bbob-vue3/package.json b/packages/bbob-vue3/package.json index 6297e86..bb7f1f8 100644 --- a/packages/bbob-vue3/package.json +++ b/packages/bbob-vue3/package.json @@ -11,7 +11,8 @@ "dependencies": { "@bbob/core": "*", "@bbob/html": "*", - "@bbob/plugin-helper": "*" + "@bbob/plugin-helper": "*", + "@bbob/types": "*" }, "peerDependencies": { "vue": "3.x" diff --git a/packages/bbob-vue3/src/Component.ts b/packages/bbob-vue3/src/Component.ts index bbff78b..e6b7e12 100644 --- a/packages/bbob-vue3/src/Component.ts +++ b/packages/bbob-vue3/src/Component.ts @@ -1,7 +1,7 @@ import { defineComponent, h, VNode } from "vue"; import { render } from "./render"; -import type { BBobPlugins, BBobCoreOptions } from "@bbob/core"; +import type { BBobPlugins, BBobCoreOptions } from "@bbob/types"; type VueComponentProps = { container: string; diff --git a/packages/bbob-vue3/src/render.ts b/packages/bbob-vue3/src/render.ts index 90517a8..4f5bf6c 100644 --- a/packages/bbob-vue3/src/render.ts +++ b/packages/bbob-vue3/src/render.ts @@ -1,11 +1,12 @@ /* eslint-disable no-use-before-define,import/prefer-default-export */ -import core, { BBobCoreOptions, BBobPlugins } from "@bbob/core"; +import core from "@bbob/core"; import * as html from "@bbob/html"; import { h, VNodeArrayChildren } from "vue"; +import type { BBobCoreOptions, BBobPlugins, TagNodeTree } from "@bbob/types"; + import { TagNode, - TagNodeTree, isStringNode, isTagNode, } from "@bbob/plugin-helper"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91a79d5..fb70fca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -160,6 +160,9 @@ importers: benchmark: specifier: 2.1.4 version: 2.1.4 + cpupro: + specifier: '*' + version: 0.5.0 picocolors: specifier: 1.0.0 version: 1.0.0 @@ -216,6 +219,9 @@ importers: '@bbob/plugin-helper': specifier: '*' version: link:../bbob-plugin-helper + '@bbob/types': + specifier: '*' + version: link:../bbob-types packages/bbob-html: dependencies: @@ -225,23 +231,37 @@ importers: '@bbob/plugin-helper': specifier: '*' version: link:../bbob-plugin-helper + '@bbob/types': + specifier: '*' + version: link:../bbob-types packages/bbob-parser: dependencies: '@bbob/plugin-helper': specifier: '*' version: link:../bbob-plugin-helper + '@bbob/types': + specifier: '*' + version: link:../bbob-types - packages/bbob-plugin-helper: {} + packages/bbob-plugin-helper: + dependencies: + '@bbob/types': + specifier: '*' + version: link:../bbob-types packages/bbob-preset: dependencies: - '@bbob/core': - specifier: '*' - version: link:../bbob-core '@bbob/plugin-helper': specifier: '*' version: link:../bbob-plugin-helper + '@bbob/types': + specifier: '*' + version: link:../bbob-types + devDependencies: + '@bbob/core': + specifier: '*' + version: link:../bbob-core packages/bbob-preset-html5: dependencies: @@ -251,6 +271,9 @@ importers: '@bbob/preset': specifier: '*' version: link:../bbob-preset + '@bbob/types': + specifier: '*' + version: link:../bbob-types devDependencies: '@bbob/core': specifier: '*' @@ -261,29 +284,21 @@ importers: packages/bbob-preset-react: dependencies: - '@bbob/preset': - specifier: '*' - version: link:../bbob-preset '@bbob/preset-html5': specifier: '*' version: link:../bbob-preset-html5 - devDependencies: - '@bbob/core': + '@bbob/types': specifier: '*' - version: link:../bbob-core + version: link:../bbob-types packages/bbob-preset-vue: dependencies: - '@bbob/preset': - specifier: '*' - version: link:../bbob-preset '@bbob/preset-html5': specifier: '*' version: link:../bbob-preset-html5 - devDependencies: - '@bbob/core': + '@bbob/types': specifier: '*' - version: link:../bbob-core + version: link:../bbob-types packages/bbob-react: dependencies: @@ -296,6 +311,9 @@ importers: '@bbob/plugin-helper': specifier: '*' version: link:../bbob-plugin-helper + '@bbob/types': + specifier: '*' + version: link:../bbob-types devDependencies: '@bbob/preset-react': specifier: ^3.0.2 @@ -316,6 +334,8 @@ importers: specifier: 18.x version: 18.2.0(react@18.2.0) + packages/bbob-types: {} + packages/bbob-vue2: dependencies: '@bbob/core': @@ -327,6 +347,9 @@ importers: '@bbob/plugin-helper': specifier: '*' version: link:../bbob-plugin-helper + '@bbob/types': + specifier: '*' + version: link:../bbob-types devDependencies: '@bbob/preset-vue': specifier: '*' @@ -352,6 +375,9 @@ importers: '@bbob/plugin-helper': specifier: '*' version: link:../bbob-plugin-helper + '@bbob/types': + specifier: '*' + version: link:../bbob-types devDependencies: '@bbob/preset-vue': specifier: '*' @@ -2191,7 +2217,6 @@ packages: /@discoveryjs/json-ext@0.5.7: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - dev: true /@endemolshinegroup/cosmiconfig-typescript-loader@3.0.2(cosmiconfig@7.1.0)(typescript@4.9.5): resolution: {integrity: sha512-QRVtqJuS1mcT56oHpVegkKBlgtWjXw/gHNWO3eL9oyB5Sc7HBoc2OLG/nYpVfT/Jejvo3NUrD0Udk7XgoyDKkA==} @@ -5777,7 +5802,6 @@ packages: /ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - dev: true /ansi-escapes@3.2.0: resolution: {integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==} @@ -6650,6 +6674,13 @@ packages: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} dev: true + /clap@3.1.1: + resolution: {integrity: sha512-vp42956Ax06WwaaheYEqEOgXZ3VKJxgccZ0gJL0HpyiupkIS9RVJFo5eDU1BPeQAOqz+cclndZg4DCqG1sJReQ==} + engines: {node: ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + dependencies: + ansi-colors: 4.1.3 + dev: false + /clean-css@5.3.3: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} @@ -7326,6 +7357,16 @@ packages: typescript: 5.1.6 dev: true + /cpupro@0.5.0: + resolution: {integrity: sha512-NEoLapQrQwrSrEBj5c9vxyUHSYiUv2aGOJ3EuO8g0j5zc7rxdNzzvT1PPY7G1H2guXBQrziHUvgdg8NkZYGMeA==} + hasBin: true + dependencies: + '@discoveryjs/json-ext': 0.5.7 + clap: 3.1.1 + open: 8.4.2 + v8-profiler-next: 1.5.1 + dev: false + /create-jest@29.7.0(@types/node@20.4.5): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7819,7 +7860,6 @@ packages: /define-lazy-prop@2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} - dev: true /define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} @@ -10173,7 +10213,6 @@ packages: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} hasBin: true - dev: true /is-extendable@0.1.1: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} @@ -10419,7 +10458,6 @@ packages: engines: {node: '>=8'} dependencies: is-docker: 2.2.1 - dev: true /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -12081,6 +12119,10 @@ packages: thenify-all: 1.6.0 dev: true + /nan@2.19.0: + resolution: {integrity: sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==} + dev: false + /nanoid@3.3.6: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -12653,7 +12695,6 @@ packages: define-lazy-prop: 2.0.0 is-docker: 2.2.1 is-wsl: 2.2.0 - dev: true /opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} @@ -15994,6 +16035,13 @@ packages: resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} dev: true + /v8-profiler-next@1.5.1: + resolution: {integrity: sha512-7gnfJ3x7zN3gzmVs69OvJBNze5dLCIeY2Necy+IzomWmprSCRsBbCn5GfwAJHkWJxzoex3gyRexxvYGlgR93yg==} + requiresBuild: true + dependencies: + nan: 2.19.0 + dev: false + /v8-to-istanbul@9.1.3: resolution: {integrity: sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==} engines: {node: '>=10.12.0'} diff --git a/tsconfig.json b/tsconfig.json index d98f215..caef933 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "dom.iterable", "esnext" ], + "baseUrl": ".", "checkJs": false, "skipLibCheck": true, "strict": true,