Created
December 24, 2020 23:06
-
-
Save jenya239/3896b05de547462fdafc87616b3a9717 to your computer and use it in GitHub Desktop.
literals.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Root } from './file' | |
import { Builder } from './builder' | |
const DIR_NAME = '' | |
const main = () => { | |
const root = new Root(DIR_NAME) | |
new Builder(root) | |
} | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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