Skip to content

Commit

Permalink
feat(@formatjs/cli-lib): add support for gts, gjs and hbs files.
Browse files Browse the repository at this point in the history
  • Loading branch information
kiwiupover authored and longlho committed May 5, 2024
1 parent 6773452 commit 1693515
Show file tree
Hide file tree
Showing 17 changed files with 811 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .swcrc
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@
},
"keepClassNames": false
}
}
}
25 changes: 23 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
"@bazel/ibazel": "0.24",
"@commitlint/cli": "17",
"@commitlint/config-angular": "17",
"@glimmer/env": "^0.1.7",
"@glimmer/reference": "^0.91.1",
"@glimmer/syntax": "^0.91.1",
"@glimmer/validator": "^0.91.1",
"@jest/transform": "29",
"@jest/types": "29",
"@lerna-lite/cli": "2",
Expand Down Expand Up @@ -88,6 +92,7 @@
"clsx": "2",
"commander": "8",
"core-js": "^3.6.5",
"ember-template-recast": "^6.1.4",
"emoji-regex": "^10.2.1",
"eslint": "7 || 8",
"fast-glob": "^3.2.7",
Expand Down Expand Up @@ -137,10 +142,22 @@
},
"resolutions": {
"terser": "5.27.0",
"source-map-js": "1.0.2"
"source-map-js": "1.0.2",
"@glimmer/syntax": "0.91.1",
"@glimmer/interfaces": "0.91.2"
},
"pnpm": {
"packageExtensions": {
"css-tree": {
"dependencies": {
"source-map-js": "*"
}
},
"postcss": {
"dependencies": {
"source-map-js": "*"
}
},
"ts-jest": {
"dependencies": {
"@jest/transform": "*",
Expand All @@ -166,12 +183,16 @@
},
"patchedDependencies": {
"@commitlint/rules@17.0.0": "patches/@commitlint__rules@17.0.0.patch",
"make-plural-compiler@5.1.0": "patches/make-plural-compiler@5.1.0.patch"
"make-plural-compiler@5.1.0": "patches/make-plural-compiler@5.1.0.patch",
"content-tag@2.0.1": "patches/content-tag@2.0.1.patch",
"@glimmer/syntax@0.91.1": "patches/@glimmer__syntax@0.91.1.patch",
"@glimmer/interfaces@0.91.2": "patches/@glimmer__interfaces@0.91.2.patch"
}
},
"author": "Seth Bertalotto <sbertal@verizonmedia.com>",
"dependencies": {
"@swc/helpers": "^0.5.1",
"content-tag": "^2.0.1",
"jest-cli": "^29.5.0",
"jest-junit": "^16.0.0",
"magic-string": "^0.30.0"
Expand Down
12 changes: 11 additions & 1 deletion packages/cli-lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ VUE_DEPS = [
"//:node_modules/vue",
]

GLIMMER_HBS_DEPS = [
"//:node_modules/@glimmer/env",
"//:node_modules/@glimmer/reference",
"//:node_modules/@glimmer/syntax",
"//:node_modules/@glimmer/validator",
"//:node_modules/content-tag",
"//:node_modules/ember-template-recast",
":node_modules/@babel/parser",
]

SRC_DEPS = [
"//:node_modules/@types/estree",
"//:node_modules/@types/fs-extra",
Expand All @@ -49,7 +59,7 @@ SRC_DEPS = [
"//:node_modules/typescript",
":node_modules/@formatjs/icu-messageformat-parser",
":node_modules/@formatjs/ts-transformer",
] + VUE_DEPS
] + VUE_DEPS + GLIMMER_HBS_DEPS

ts_compile_node(
name = "dist",
Expand Down
6 changes: 6 additions & 0 deletions packages/cli-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,20 @@
"url": "https://github.com/formatjs/formatjs/issues"
},
"dependencies": {
"@babel/parser": "^7.22.10",
"@formatjs/icu-messageformat-parser": "workspace:*",
"@formatjs/ts-transformer": "workspace:*",
"@glimmer/env": "^0.1.7",
"@glimmer/reference": "^0.91.1",
"@glimmer/syntax": "^0.91.1",
"@glimmer/validator": "^0.91.1",
"@types/estree": "^1.0.0",
"@types/fs-extra": "^9.0.1",
"@types/json-stable-stringify": "^1.0.32",
"@types/node": "14 || 16 || 17",
"chalk": "^4.0.0",
"commander": "8",
"ember-template-recast": "^6.1.4",
"fast-glob": "^3.2.7",
"fs-extra": "10",
"json-stable-stringify": "^1.0.1",
Expand Down
16 changes: 15 additions & 1 deletion packages/cli-lib/src/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,20 @@ async function processFile(
debug('Processing %s using vue extractor', fn)
const {parseFile} = await import('./vue_extractor.js')
parseFile(source, fn, scriptParseFn)
} else if (fn.endsWith('.hbs')) {
debug('Processing %s using hbs extractor', fn)
// SAFETY: The TS config does not understand that this is a
// safe way to import a module in a cjs project
// @ts-ignore
const {parseFile} = await import('./hbs_extractor.js')
parseFile(source, fn, opts)
} else if (fn.endsWith('.gts') || fn.endsWith('.gjs')) {
debug('Processing %s as gts/gjs file', fn)
// SAFETY: The TS config does not understand that this is a
// safe way to import a module in a cjs project
// @ts-ignore
const {parseFile} = await import('./gts_extractor.js')
parseFile(source, fn, opts)
} else {
debug('Processing %s using typescript extractor', fn)
scriptParseFn(source)
Expand Down Expand Up @@ -219,7 +233,7 @@ export async function extract(
const {id, description, defaultMessage} = message
if (!id) {
const error = new Error(
`[FormatJS CLI] Missing message id for message:
`[FormatJS CLI] Missing message id for message:
${JSON.stringify(message, undefined, 2)}`
)
if (throws) {
Expand Down
18 changes: 18 additions & 0 deletions packages/cli-lib/src/gts_extractor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {Preprocessor} from 'content-tag'
import {parseFile as parseHbsFile} from './hbs_extractor'
import {parseScript} from './parse_script'
let p = new Preprocessor()

export function parseFile(source: string, fileName: string, options: any) {
const scriptParseFn = parseScript(options, fileName)
const transformedSource = p.process(source, {filename: fileName})

scriptParseFn(transformedSource)

// extract template from transformed source to then run through hbs processor
const parseResult = p.parse(source, {filename: fileName})

for (let parsed of parseResult) {
parseHbsFile(parsed.contents, fileName, options)
}
}
59 changes: 59 additions & 0 deletions packages/cli-lib/src/hbs_extractor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {transform} from 'ember-template-recast'
import type {AST} from '@glimmer/syntax'
import {Opts} from '@formatjs/ts-transformer'

function extractText(
node: AST.MustacheStatement | AST.SubExpression,
fileName: string,
options: Opts
) {
if (!options.onMsgExtracted) return
if (!options.overrideIdFn) return

if (node.path.type !== 'PathExpression') return

if (['format-message', 'formatMessage'].includes(node.path.original)) {
let [first, second] = node.params

if (first.type !== 'StringLiteral') return

let message = first?.value

let desc: string | undefined
if (second?.type === 'StringLiteral') {
desc = second.value?.trim().replace(/\s+/gm, ' ')
}

let defaultMessage = message?.trim().replace(/\s+/gm, ' ')

let id =
typeof options.overrideIdFn === 'string'
? options.overrideIdFn
: options.overrideIdFn(undefined, defaultMessage, desc, fileName)

options.onMsgExtracted(fileName, [
{
id: id,
defaultMessage: defaultMessage,
description: desc,
},
])
}
}

export function parseFile(source: string, fileName: string, options: any) {
let visitor = function () {
return {
MustacheStatement(node: AST.MustacheStatement) {
extractText(node, fileName, options)
},
SubExpression(node: AST.SubExpression) {
extractText(node, fileName, options)
},
}
}

// SAFETY: ember-template-recast's types are out of date,
// but it does not affect runtime
transform(source, visitor as any)
}
87 changes: 87 additions & 0 deletions packages/cli-lib/tests/unit/fixtures/comp.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import formatMessage from 'ember-intl/helpers/format-message';
import or from 'ember-truth-helpers/helpers/or';
import Component from '@glimmer/component';
import {service} from '@ember/service';

/*
* We don't want to support ids passed to formatMessage in templates,
* because the accidental risk of collision is too high.
*
* They are supported in JS because it's an API that we don't control.
*/
const Header = <template>
<header>{{formatMessage "G'day!, from a secondary component in the same GJS file"}}</header>
</template>;

export default class Comp extends Component {
@service intl;

get message() {
return this.intl.formatMessage({
defaultMessage: 'js getter with an id',
id: 'getter-message',
});
}

get message2() {
return this.intl.formatMessage({
defaultMessage: 'js getter with no id',
});
}

<template>
<Header />
<p>
{{formatMessage 'in template' 'in template desc'}}
</p>

<p>
{{formatMessage
'{connectorName, select,
none {Install Service}
other {Install {connectorName}}
}'
name=(or this.name 'none')
}}
</p>

<!-- prettier-ignore -->
<p>
{{formatMessage
'{connectorName, select,
none {Install Service}
other {Install {connectorName}}
}'
name=(or this.name 'none')
}}
</p>

<p>
{{formatMessage
"Very long message with
multiple'' breaklines
and multiple spaces
'<a href={href}>' Link '</a>'"
'Nice description'
href='/whatever/link'
htmlSafe=true
}}
</p>

<!-- this is the same as above but is nested so it contains more whitespaces -->
<div>
<p>
{{formatMessage
"Very long message with
multiple'' breaklines
and multiple spaces
'<a href={href}>' Link '</a>'"
'Nice
description'
href='/whatever/link'
htmlSafe=true
}}
</p>
</div>
</template>
}

0 comments on commit 1693515

Please sign in to comment.