Skip to content

Commit

Permalink
BEMBlock class
Browse files Browse the repository at this point in the history
  • Loading branch information
mickmao committed Sep 25, 2023
1 parent 09b5470 commit cc57e0c
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 137 deletions.
51 changes: 51 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"version": "1.0.0",
"description": "Parses a BEM file into a TypeScript type.",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1"
Expand All @@ -26,6 +25,8 @@
"@types/node": "^20.6.5",
"bem-neon": "^1.0.0",
"meow": "^12.1.1",
"param-case": "^3.0.4",
"pascal-case": "^3.1.2",
"typescript": "^5.2.2"
},
"devDependencies": {
Expand Down
164 changes: 156 additions & 8 deletions src/BEMBlock.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,157 @@
export type BEMBlock<
TName extends string,
TElements extends Record<string, string | never>,
TModifiers extends string | undefined
> = {
name: TName
elements: TElements
modifiers: TModifiers
import { parseBEM } from 'bem-neon'
import ts, { factory } from 'typescript'
import { pascalCase } from 'pascal-case'
import { paramCase } from 'param-case'
import { EOL } from 'node:os'

export type ParseOptions = {}

export class BEMBlock {
#block: ReturnType<typeof parseBEM>

public constructor(bem: string, _options: ParseOptions = {}) {
this.#block = parseBEM(bem)
}

public get name() {
return paramCase(this.#block.name)
}

public set name(value) {
this.#block.name = value
}

public get elements() {
return structuredClone(this.#block.elements)
}

public set elements(value) {
this.#block.elements = value
}

public get modifiers() {
return structuredClone(this.#block.modifiers)
}

public set modifiers(value) {
this.#block.modifiers = value
}

/**
* @returns type AST generated by the TypeScript Compiler API.
*/
public toTypeAST() {
return factory.createTypeAliasDeclaration(
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
factory.createIdentifier(pascalCase(`${this.#block.name}Block`)),
undefined,
factory.createTypeLiteralNode([
factory.createPropertySignature(
undefined,
factory.createIdentifier('name'),
undefined,
factory.createLiteralTypeNode(factory.createStringLiteral(this.name))
),
factory.createPropertySignature(
undefined,
factory.createIdentifier('elements'),
undefined,
factory.createTypeLiteralNode(
this.#block.elements.map((element) =>
factory.createPropertySignature(
undefined,
factory.createIdentifier(paramCase(element.name)),
undefined,
element.modifiers.length
? factory.createUnionTypeNode(
element.modifiers.map((modifier) =>
factory.createLiteralTypeNode(
factory.createStringLiteral(paramCase(modifier))
)
)
)
: factory.createKeywordTypeNode(
ts.SyntaxKind.UndefinedKeyword
)
)
)
)
),
factory.createPropertySignature(
undefined,
factory.createIdentifier('modifiers'),
undefined,
this.#block.modifiers.length
? factory.createUnionTypeNode(
this.#block.modifiers.map((modifier) =>
factory.createLiteralTypeNode(
factory.createStringLiteral(paramCase(modifier))
)
)
)
: factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword)
)
])
)
}

/**
* @returns the TypeScript type that represents the BEM structure.
* @example
* fooBlock.toType()
* `export const FooBlock = {
* name: 'foo'
* elements: {
* qux: undefined
* }
* modifiers: 'bar' | 'baz'
* }`
*/
public toType(printerOptions?: ts.PrinterOptions) {
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
omitTrailingSemicolon: true,
...printerOptions
})

return printer.printNode(
ts.EmitHint.Unspecified,
this.toTypeAST(),
ts.createSourceFile(
'block.ts',
'',
ts.ScriptTarget.Latest,
false,
ts.ScriptKind.TS
)
)
}

/**
* @param eol defualts to `os.EOL`
* @returns the raw BEM file format string.
* @example fooBlock.toString() // foo[bar,baz]\nqux
*/
public toString(eol = EOL) {
let result = this.name
if (this.modifiers.length) {
result += `[${this.modifiers.join(',')}]`
}
for (const element of this.elements) {
result += eol + element.name
if (element.modifiers.length) {
result += `[${element.modifiers.join(',')}]`
}
}
result += eol
return result
}

public valueOf() {
return structuredClone(this.#block)
}

public toJSON() {
return this.valueOf()
}
}
104 changes: 1 addition & 103 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,103 +1 @@
import { parseBEM } from 'bem-neon'
import type { BEMBlock } from './BEMBlock.js'
import ts, { factory } from 'typescript'

export type ParseOptions = {}

export function parse(
bem: string,
_options: ParseOptions = {}
): BEMBlock<string, Record<string, string | never>, string | undefined> {
const block = parseBEM(bem)
const blockType = factory.createTypeAliasDeclaration(
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
factory.createIdentifier(`${block.name}Block`),
undefined,
factory.createTypeLiteralNode([
factory.createPropertySignature(
undefined,
factory.createIdentifier('name'),
undefined,
factory.createLiteralTypeNode(factory.createStringLiteral(block.name))
),
factory.createPropertySignature(
undefined,
factory.createIdentifier('elements'),
undefined,
factory.createTypeLiteralNode(
block.elements.map((element) =>
factory.createPropertySignature(
undefined,
factory.createIdentifier(element.name),
undefined,
element.modifiers.length
? factory.createUnionTypeNode(
element.modifiers.map((modifier) =>
factory.createLiteralTypeNode(
factory.createStringLiteral(modifier)
)
)
)
: factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
)
)
)
),
factory.createPropertySignature(
undefined,
factory.createIdentifier('modifiers'),
undefined,
block.modifiers.length
? factory.createUnionTypeNode(
block.modifiers.map((modifier) =>
factory.createLiteralTypeNode(
factory.createStringLiteral(modifier)
)
)
)
: factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword)
)
])
)

// // Create a source file
// const sourceFile = factory.createSourceFile(
// 'foo.ts',
// '',
// ts.ScriptTarget.Latest,
// false,
// ts.ScriptKind.TS
// )

// // // Add the type alias declaration to the source file
// const updatedSourceFile = ts.updateSourceFile(sourceFile, [blockType])

// Create a printer to output the TypeScript code
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed
})

const result = printer.printNode(
ts.EmitHint.Unspecified,
blockType,
ts.createSourceFile(
'block.ts',
'',
ts.ScriptTarget.Latest,
false,
ts.ScriptKind.TS
)
)

// // Print the type alias declaration
// const result = printer.printNode(
// ts.EmitHint.Unspecified,
// blockType,
// updatedSourceFile
// )

// Output the result
console.log(`export ${result}`)

return { name: block.name, elements: {}, modifiers: '' }
}
export * from './BEMBlock.js'
Loading

0 comments on commit cc57e0c

Please sign in to comment.