Skip to content

Instantly share code, notes, and snippets.

@jenya239
Created December 24, 2020 23:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jenya239/3896b05de547462fdafc87616b3a9717 to your computer and use it in GitHub Desktop.
Save jenya239/3896b05de547462fdafc87616b3a9717 to your computer and use it in GitHub Desktop.
literals.ts
import * as fs from 'fs'
import * as pathModule from 'path'
import { Dir, DirItem, Root, File } from './file'
interface IResNode {
[key: string]: IResNode | string
}
const OUTPUT_JSON = './src/utils/settings.json'
export class Builder {
root: Root
res: IResNode
constructor(root: Root) {
this.root = root
this.res = {}
this.processItem(this.root, this.res)
fs.writeFileSync(OUTPUT_JSON, JSON.stringify(this.res, null, 2))
}
processItem(item: DirItem, parent: IResNode): void {
if (item instanceof Dir) {
const dir = item as Dir
if (dir.children.length > 0) {
const res: IResNode = (parent[item.name] = {})
for (const child of dir.children) {
this.processItem(child, res)
}
}
} else if (item instanceof File) {
const file = item as File
if (file.occurrencies.length > 0) {
const res: IResNode = (parent[item.name] = {})
for (const occurrence of file.occurrencies) {
res[occurrence.getName()] = occurrence.getValue()
}
this.processFile(file)
}
}
}
processFile(file: File): void {
let res = file.parts[0]
file.occurrencies.forEach((occurrence, index) => {
res += occurrence.getReplacement() + file.parts[index + 1]
})
fs.writeFileSync(file.path, res)
}
}
import * as fs from 'fs'
import * as pathModule from 'path'
import * as ts from 'typescript'
import { ImportDeclaration, JsxAttribute, JsxText, SourceFile, StringLiteral } from 'typescript'
import { AttrOccurrence, Occurrence, StringOccurrence, TextOccurrence } from './occurrence'
const forEachParent = (node: ts.Node | null, f: (parentNode: ts.Node) => void) => {
while (node) {
f(node)
node = node.parent
}
}
export abstract class DirItem {
entry: fs.Dirent
path: string
name: string
ext: string
parent: Dir
constructor(parent: Dir, entry: fs.Dirent) {
this.parent = parent
this.entry = entry
this.path = pathModule.join(parent.path, entry.name)
this.ext = pathModule.extname(this.path)
this.name = pathModule.basename(this.path, this.ext)
}
}
export class Dir extends DirItem {
children: DirItem[]
constructor(parent: Dir, entry: fs.Dirent) {
super(parent, entry)
this.children = []
if (['@types', 'assets'].includes(this.name)) {
return
}
console.group(this.name)
for (const item of fs.readdirSync(this.path, { withFileTypes: true })) {
if (item.isDirectory()) {
this.children.push(new Dir(this, item))
} else if (['.ts', '.tsx'].includes(pathModule.extname(item.name))) {
this.children.push(new File(this, item))
}
}
console.groupEnd()
}
}
export class File extends DirItem {
source: string
sourceFile: SourceFile
textIndex = 0
replacer: any
occurrencies: Occurrence[]
lastImport: ImportDeclaration
parts: string[]
constructor(parent: Dir, entry: fs.Dirent) {
super(parent, entry)
this.occurrencies = []
if (
[
'langCodes',
'countryCodes',
'serviceWorker',
'data',
'api-types',
'preventBodyScroll',
'PerspectiveView',
'Banner',
'settings',
].includes(this.name)
) {
return
}
console.group(this.name + ' ' + this.breadcrumb())
this.source = fs.readFileSync(this.path, { encoding: 'utf8' })
this.sourceFile = ts.createSourceFile(this.path, this.source, ts.ScriptTarget.ES2015, true)
this.findLastImport()
this.processNode(this.sourceFile)
console.groupEnd()
let start = 0
this.parts = []
for (const occurrence of this.occurrencies) {
this.parts.push(this.source.substring(start, occurrence.getStart()))
start = occurrence.getEnd()
}
this.parts.push(this.source.substring(start))
if (this.lastImport) {
this.parts[0] =
this.parts[0].substring(0, this.lastImport.end) +
this.settingsInit() +
this.parts[0].substr(this.lastImport.end)
}
}
processNode(node: ts.Node): void {
if (node.kind === ts.SyntaxKind.JsxText) {
const textNode = node as JsxText
if (/\w{2,}/.test(textNode.text)) {
new TextOccurrence(this, node as JsxText)
}
} else if (node.kind === ts.SyntaxKind.JsxAttribute) {
const attr = node as JsxAttribute
const name = attr.name.getText(this.sourceFile)
if (
attr.initializer?.kind === ts.SyntaxKind.StringLiteral &&
['title', 'placeholder', 'error'].includes(name)
) {
new AttrOccurrence(this, attr)
}
} else if (
node.kind === ts.SyntaxKind.StringLiteral &&
![
ts.SyntaxKind.JsxAttribute,
ts.SyntaxKind.ImportDeclaration,
ts.SyntaxKind.ModuleDeclaration,
ts.SyntaxKind.LiteralType,
].includes(node.parent.kind)
) {
const literal = node as StringLiteral
const t = literal.text.replace(/ /g, '')
if (/\s/.test(t) && /\w{2,}/.test(t) && !/ ga_/.test(t)) {
let inStyle = false
forEachParent(literal, (node) => {
if (
(node.kind === ts.SyntaxKind.JsxAttribute &&
(node as JsxAttribute).name.text === 'style') ||
node.kind === ts.SyntaxKind.TaggedTemplateExpression
) {
inStyle = true
}
})
if (!inStyle) {
new StringOccurrence(this, literal)
}
}
}
node.forEachChild((child) => this.processNode(child))
}
breadcrumb(): string {
const res: string[] = []
// eslint-disable-next-line @typescript-eslint/no-this-alias
let cur: DirItem = this
do {
res.unshift(cur.name)
cur = cur.parent
} while (!(cur instanceof Root))
res.unshift(cur.name)
return res.join('.')
}
findLastImport(): void {
this.sourceFile.forEachChild((node) => {
if (node.kind === ts.SyntaxKind.ImportDeclaration) this.lastImport = node as ImportDeclaration
})
}
settingsInit(): string {
return (
"\nimport * as settingsJson from 'utils/settings.json'\n" +
`const settings = settingsJson.${this.breadcrumb()}`
)
}
}
export class Root extends Dir {
constructor(path: string) {
super(
{ path: pathModule.dirname(path) } as Dir,
{ name: pathModule.basename(path) } as fs.Dirent
)
}
}
import { Root } from './file'
import { Builder } from './builder'
const DIR_NAME = ''
const main = () => {
const root = new Root(DIR_NAME)
new Builder(root)
}
main()
import { JsxAttribute, JsxText, StringLiteral } from 'typescript'
import { File } from './file'
export abstract class Occurrence {
file: File
index: number
constructor(file: File) {
this.file = file
this.index = this.file.occurrencies.length
this.file.occurrencies.push(this)
}
abstract getName(): string
abstract getValue(): string
abstract getStart(): number
abstract getEnd(): number
abstract getReplacement(): string
createName(text: string): string {
let s = text.trim().toLowerCase().replace(/\s+/g, '_')
s = s.replace(/[^\w]/g, '')
s = s.replace(/_+/g, '_')
return s
}
}
export class AttrOccurrence extends Occurrence {
attr: JsxAttribute
constructor(file: File, attr: JsxAttribute) {
super(file)
this.attr = attr
console.log(this.constructor.name, this.getName(), attr.name.text, this.getValue())
}
getName(): string {
return this.createName((this.attr.initializer as StringLiteral).text)
}
getValue(): string {
return (this.attr.initializer as StringLiteral).text
}
getEnd(): number {
return this.attr.getEnd()
}
getStart(): number {
return this.attr.getStart()
}
getReplacement(): string {
return `{settings.${this.getName()}}`
}
}
export class TextOccurrence extends Occurrence {
text: JsxText
constructor(file: File, text: JsxText) {
super(file)
this.text = text
console.log(this.constructor.name, this.getName(), this.getValue())
}
getName(): string {
return this.createName(this.text.text)
}
getValue(): string {
return this.text.text.trim()
}
getEnd(): number {
return this.text.getEnd()
}
getStart(): number {
return this.text.getStart()
}
getReplacement(): string {
return `{settings.${this.getName()}}`
}
}
export class StringOccurrence extends Occurrence {
string: StringLiteral
constructor(file: File, string: StringLiteral) {
super(file)
this.string = string
console.log(this.constructor.name, this.getName(), this.getValue())
}
getName(): string {
return this.createName(this.string.text)
}
getValue(): string {
return this.string.text
}
getEnd(): number {
return this.string.getEnd()
}
getStart(): number {
return this.string.getStart()
}
getReplacement(): string {
return `settings.${this.getName()}`
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment