Skip to content

Commit c60d261

Browse files
committed
add codegen
1 parent 6f2f7e4 commit c60d261

22 files changed

+1896
-21
lines changed

package.json

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,24 @@
2727
}
2828
},
2929
"dependencies": {
30+
"@intlify/core-base": "^9.0.0-beta.16",
31+
"@intlify/message-compiler": "^9.0.0-beta.16",
32+
"@intlify/shared": "^9.0.0-beta.16",
3033
"chalk": "^4.1.0",
3134
"debug": "^4.3.1",
32-
"meow": "^8.0.0"
35+
"jsonc-eslint-parser": "^0.6.0",
36+
"meow": "^8.0.0",
37+
"yaml-eslint-parser": "^0.2.0"
3338
},
3439
"devDependencies": {
40+
"@microsoft/api-extractor": "^7.12.0",
3541
"@types/debug": "^4.1.5",
3642
"@types/jest": "^26.0.15",
3743
"@types/meow": "^5.0.0",
3844
"@types/node": "^14.14.9",
3945
"@typescript-eslint/eslint-plugin": "^4.11.0",
4046
"@typescript-eslint/parser": "^4.11.0",
47+
"api-docs-gen": "^0.2.6",
4148
"eslint": "^7.16.0",
4249
"eslint-config-prettier": "^6.15.0",
4350
"eslint-plugin-prettier": "^3.1.4",
@@ -49,6 +56,7 @@
4956
"opener": "^1.5.1",
5057
"prettier": "^2.2.0",
5158
"shipjs": "^0.23.0",
59+
"source-map": "^0.6.1",
5260
"ts-jest": "^26.4.4",
5361
"typescript": "^4.1.3",
5462
"typescript-eslint-language-service": "^4.1.2"
@@ -70,6 +78,10 @@
7078
],
7179
"license": "MIT",
7280
"main": "lib/index.js",
81+
"repository": {
82+
"type": "git",
83+
"url": "git+https://github.com/intlify/cli.git"
84+
},
7385
"scripts": {
7486
"build": "tsc -p .",
7587
"clean": "npm-run-all clean:*",
@@ -89,9 +101,5 @@
89101
"test:unit": "jest --env node",
90102
"test:watch": "jest --env node --watch",
91103
"watch": "tsc -p . --watch"
92-
},
93-
"repository": {
94-
"type": "git",
95-
"url": "git+https://github.com/intlify/cli.git"
96104
}
97105
}

src/generator/codegen.ts

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
import {
2+
LocationStub,
3+
baseCompile,
4+
CompileError,
5+
ResourceNode,
6+
CompileOptions
7+
} from '@intlify/message-compiler'
8+
import {
9+
SourceMapGenerator,
10+
SourceMapConsumer,
11+
MappedPosition,
12+
MappingItem
13+
} from 'source-map'
14+
15+
import type { RawSourceMap } from 'source-map'
16+
17+
export type DevEnv = 'development' | 'production'
18+
19+
export interface Position {
20+
line: number
21+
column: number
22+
offset?: number
23+
}
24+
25+
export interface SourceLocationable {
26+
loc?: {
27+
start: Position
28+
end: Position
29+
}
30+
}
31+
32+
export interface CodeGenOptions {
33+
type?: 'plain' | 'sfc'
34+
source?: string
35+
sourceMap?: boolean
36+
filename?: string
37+
inSourceMap?: RawSourceMap
38+
isGlobal?: boolean
39+
locale?: string
40+
env?: DevEnv
41+
forceStringify?: boolean
42+
onWarn?: (msg: string) => void
43+
onError?: (msg: string) => void
44+
}
45+
46+
export interface CodeGenContext {
47+
source?: string
48+
code: string
49+
indentLevel: number
50+
filename: string
51+
line: number
52+
column: number
53+
offset: number
54+
env: DevEnv
55+
forceStringify: boolean
56+
map?: SourceMapGenerator
57+
}
58+
59+
export interface CodeGenerator {
60+
context(): CodeGenContext
61+
push<Node extends SourceLocationable>(
62+
code: string,
63+
node?: Node,
64+
name?: string
65+
): void
66+
indent(withNewLine?: boolean): void
67+
deindent(withNewLine?: boolean): void
68+
newline(): void
69+
pushline<Node extends SourceLocationable>(
70+
code: string,
71+
node?: Node,
72+
name?: string
73+
): void
74+
}
75+
76+
export interface CodeGenResult<ASTNode> {
77+
code: string
78+
ast: ASTNode
79+
map?: RawSourceMap
80+
}
81+
82+
export function createCodeGenerator(
83+
options: CodeGenOptions = {
84+
filename: 'bundle.json',
85+
sourceMap: false,
86+
env: 'development',
87+
forceStringify: false
88+
}
89+
): CodeGenerator {
90+
const { sourceMap, source, filename } = options
91+
const _context = Object.assign(
92+
{
93+
code: '',
94+
column: 1,
95+
line: 1,
96+
offset: 0,
97+
map: undefined,
98+
indentLevel: 0
99+
},
100+
options
101+
) as CodeGenContext
102+
103+
const context = (): CodeGenContext => _context
104+
105+
function push<Node extends SourceLocationable>(
106+
code: string,
107+
node?: Node,
108+
name?: string
109+
): void {
110+
_context.code += code
111+
if (_context.map) {
112+
if (node && node.loc && node.loc !== LocationStub) {
113+
addMapping(node.loc.start, name)
114+
}
115+
advancePositionWithSource(_context as Position, code)
116+
}
117+
}
118+
119+
function _newline(n: number): void {
120+
push('\n' + ` `.repeat(n))
121+
}
122+
123+
function indent(withNewLine = true): void {
124+
const level = ++_context.indentLevel
125+
withNewLine && _newline(level)
126+
}
127+
128+
function deindent(withNewLine = true): void {
129+
const level = --_context.indentLevel
130+
withNewLine && _newline(level)
131+
}
132+
133+
function newline(): void {
134+
_newline(_context.indentLevel)
135+
}
136+
137+
function pushline<Node extends SourceLocationable>(
138+
code: string,
139+
node?: Node,
140+
name?: string
141+
): void {
142+
push(code, node, name)
143+
newline()
144+
}
145+
146+
function addMapping(loc: Position, name?: string) {
147+
_context.map!.addMapping({
148+
name,
149+
source: _context.filename,
150+
original: {
151+
line: loc.line,
152+
column: loc.column - 1
153+
},
154+
generated: {
155+
line: _context.line,
156+
column: _context.column - 1
157+
}
158+
})
159+
}
160+
161+
if (sourceMap && source) {
162+
_context.map = new SourceMapGenerator()
163+
_context.map.setSourceContent(filename!, source)
164+
}
165+
166+
return {
167+
context,
168+
push,
169+
indent,
170+
deindent,
171+
newline,
172+
pushline
173+
}
174+
}
175+
176+
function advancePositionWithSource(
177+
pos: Position,
178+
source: string,
179+
numberOfCharacters: number = source.length
180+
): Position {
181+
if (pos.offset == null) {
182+
return pos
183+
}
184+
185+
let linesCount = 0
186+
let lastNewLinePos = -1
187+
for (let i = 0; i < numberOfCharacters; i++) {
188+
if (source.charCodeAt(i) === 10 /* newline char code */) {
189+
linesCount++
190+
lastNewLinePos = i
191+
}
192+
}
193+
194+
pos.offset += numberOfCharacters
195+
pos.line += linesCount
196+
pos.column =
197+
lastNewLinePos === -1
198+
? pos.column + numberOfCharacters
199+
: numberOfCharacters - lastNewLinePos
200+
201+
return pos
202+
}
203+
204+
export function generateMessageFunction(
205+
msg: string,
206+
options: CodeGenOptions
207+
): CodeGenResult<ResourceNode> {
208+
const env = options.env != null ? options.env : 'development'
209+
let occured = false
210+
const newOptions = Object.assign(options, { mode: 'arrow' }) as CompileOptions
211+
newOptions.onError = (err: CompileError): void => {
212+
options.onError && options.onError(err.message)
213+
occured = true
214+
}
215+
const { code, ast, map } = baseCompile(msg, newOptions)
216+
const genCode = !occured
217+
? env === 'development'
218+
? `(()=>{const fn=${code};fn.source=${JSON.stringify(msg)};return fn;})()`
219+
: `${code}`
220+
: msg
221+
return { code: genCode, ast, map }
222+
}
223+
224+
export function mapLinesColumns(
225+
resMap: RawSourceMap,
226+
codeMaps: Map<string, RawSourceMap>,
227+
inSourceMap?: RawSourceMap
228+
): RawSourceMap | null {
229+
if (!resMap) {
230+
return null
231+
}
232+
233+
const resMapConsumer = new SourceMapConsumer(resMap)
234+
const inMapConsumer = inSourceMap ? new SourceMapConsumer(inSourceMap) : null
235+
const mergedMapGenerator = new SourceMapGenerator()
236+
237+
let inMapFirstItem: MappingItem | null = null
238+
if (inMapConsumer) {
239+
inMapConsumer.eachMapping(m => {
240+
if (inMapFirstItem) {
241+
return
242+
}
243+
inMapFirstItem = m
244+
})
245+
}
246+
247+
resMapConsumer.eachMapping(res => {
248+
if (res.originalLine == null) {
249+
return
250+
}
251+
252+
const map = codeMaps.get(res.name)
253+
if (!map) {
254+
return
255+
}
256+
257+
let inMapOrigin: MappedPosition | null = null
258+
if (inMapConsumer) {
259+
inMapOrigin = inMapConsumer.originalPositionFor({
260+
line: res.originalLine,
261+
column: res.originalColumn - 1
262+
})
263+
if (inMapOrigin.source == null) {
264+
inMapOrigin = null
265+
return
266+
}
267+
}
268+
269+
const mapConsumer = new SourceMapConsumer(map)
270+
mapConsumer.eachMapping(m => {
271+
mergedMapGenerator.addMapping({
272+
original: {
273+
line: inMapFirstItem
274+
? inMapFirstItem.originalLine + res.originalLine - 2
275+
: res.originalLine,
276+
column: inMapFirstItem
277+
? inMapFirstItem.originalColumn + res.originalColumn
278+
: res.originalColumn
279+
},
280+
generated: {
281+
line: inMapFirstItem
282+
? inMapFirstItem.generatedLine + res.originalLine - 2
283+
: res.originalLine,
284+
// map column with message format compilation code map
285+
column: inMapFirstItem
286+
? inMapFirstItem.generatedColumn +
287+
res.originalColumn +
288+
m.generatedColumn
289+
: res.originalColumn + m.generatedColumn
290+
},
291+
source: inMapOrigin ? inMapOrigin.source : res.source,
292+
name: m.name // message format compilation code
293+
})
294+
})
295+
})
296+
297+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
298+
const generator = mergedMapGenerator as any
299+
// const targetConsumer = inMapConsumer || resMapConsumer
300+
const targetConsumer = inMapConsumer || resMapConsumer
301+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
302+
;(targetConsumer as any).sources.forEach((sourceFile: string) => {
303+
generator._sources.add(sourceFile)
304+
const sourceContent = targetConsumer.sourceContentFor(sourceFile)
305+
if (sourceContent != null) {
306+
mergedMapGenerator.setSourceContent(sourceFile, sourceContent)
307+
}
308+
})
309+
310+
generator._sourceRoot = inSourceMap
311+
? inSourceMap.sourceRoot
312+
: resMap.sourceRoot
313+
generator._file = inSourceMap ? inSourceMap.file : resMap.file
314+
315+
return generator.toJSON()
316+
}

src/generator/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { generate as generateJSON } from './json'
2+
export { generate as generateYAML } from './yaml'

0 commit comments

Comments
 (0)