@lexibase/rdx-next 6.0.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,12 @@ async function sourcePhrases(rootDir) {
21
21
  if (source === undefined)
22
22
  throw new Error(`Create ${sourceFile} to use homepage.translations.`)
23
23
  const { default: ts } = await import('typescript')
24
- const file = ts.createSourceFile(sourceFile, source, ts.ScriptTarget.Latest)
24
+ const file = ts.createSourceFile(
25
+ sourceFile,
26
+ source,
27
+ ts.ScriptTarget.Latest,
28
+ true
29
+ )
25
30
  for (const statement of file.statements) {
26
31
  if (
27
32
  !ts.isExpressionStatement(statement) ||
@@ -34,19 +39,67 @@ async function sourcePhrases(rootDir) {
34
39
  )
35
40
  }
36
41
  const phrases = new Set()
37
- function visit(node) {
42
+ const warnings = []
43
+ const passedOn =
44
+ 't is passed on or renamed; phrases translated elsewhere are left out of the translation report'
45
+ const needsLiteral =
46
+ 't() needs a literal phrase; this call is left out of the translation report'
47
+ function warn(node, message) {
48
+ const { line } = file.getLineAndCharacterOfPosition(node.getStart(file))
49
+ warnings.push({ line: line + 1, message })
50
+ }
51
+
52
+ // Bind identifiers within this file so an unrelated or shadowed t is ignored.
53
+ const options = { noLib: true, noResolve: true, types: [] }
54
+ const program = ts.createProgram([sourceFile], options, {
55
+ ...ts.createCompilerHost(options),
56
+ getSourceFile: (name) => (name === sourceFile ? file : undefined),
57
+ })
58
+ const checker = program.getTypeChecker()
59
+ const bindings = new Set()
60
+ function findBindings(node) {
38
61
  if (
39
- ts.isCallExpression(node) &&
40
- ts.isIdentifier(node.expression) &&
41
- node.expression.text === 't' &&
42
- node.arguments.length === 1 &&
43
- ts.isStringLiteralLike(node.arguments[0])
44
- )
45
- phrases.add(node.arguments[0].text)
62
+ ts.isBindingElement(node) &&
63
+ ts.isObjectBindingPattern(node.parent) &&
64
+ ts.isIdentifier(node.name)
65
+ ) {
66
+ const key = node.propertyName ?? node.name
67
+ if (ts.isStringLiteralLike(key) || ts.isIdentifier(key)) {
68
+ if (key.text === 't') {
69
+ if (node.name.text === 't')
70
+ bindings.add(checker.getSymbolAtLocation(node.name))
71
+ else warn(node, passedOn)
72
+ }
73
+ }
74
+ }
75
+ ts.forEachChild(node, findBindings)
76
+ }
77
+ findBindings(file)
78
+
79
+ function visit(node) {
80
+ if (ts.isIdentifier(node) && node.text === 't') {
81
+ const parent = node.parent
82
+ const symbol = ts.isShorthandPropertyAssignment(parent)
83
+ ? checker.getShorthandAssignmentValueSymbol(parent)
84
+ : checker.getSymbolAtLocation(node)
85
+ if (
86
+ symbol &&
87
+ bindings.has(symbol) &&
88
+ !ts.isBindingElement(parent) &&
89
+ !ts.isTypeQueryNode(parent)
90
+ ) {
91
+ if (ts.isCallExpression(parent) && parent.expression === node) {
92
+ const [argument] = parent.arguments
93
+ if (parent.arguments.length === 1 && ts.isStringLiteralLike(argument))
94
+ phrases.add(argument.text)
95
+ else warn(node, needsLiteral)
96
+ } else warn(node, passedOn)
97
+ }
98
+ }
46
99
  ts.forEachChild(node, visit)
47
100
  }
48
101
  visit(file)
49
- return phrases
102
+ return { phrases, warnings }
50
103
  }
51
104
 
52
105
  export async function readHomepageTranslations(
@@ -54,7 +107,11 @@ export async function readHomepageTranslations(
54
107
  { report = false } = {}
55
108
  ) {
56
109
  const { rootDir, localization } = project
57
- const phrases = await sourcePhrases(rootDir)
110
+ const { phrases, warnings } = await sourcePhrases(rootDir)
111
+ if (report) {
112
+ for (const { line, message } of warnings.sort((a, b) => a.line - b.line))
113
+ console.warn(`[RDX] ${sourceFile}:${line}: ${message}`)
114
+ }
58
115
  phrases.delete(metadataKey)
59
116
  const catalogs = []
60
117
  for (const locale of localization?.locales ?? []) {
@@ -86,11 +143,14 @@ export async function readHomepageTranslations(
86
143
  `[RDX] ${file}: missing translation for ${JSON.stringify(phrase)}`
87
144
  )
88
145
  }
89
- for (const phrase of Object.keys(messages)) {
90
- if (phrase !== metadataKey && !phrases.has(phrase))
91
- console.warn(
92
- `[RDX] ${file}: orphaned translation for ${JSON.stringify(phrase)}`
93
- )
146
+ // An unrecognized call may use any of the remaining keys.
147
+ if (!warnings.length) {
148
+ for (const phrase of Object.keys(messages)) {
149
+ if (phrase !== metadataKey && !phrases.has(phrase))
150
+ console.warn(
151
+ `[RDX] ${file}: orphaned translation for ${JSON.stringify(phrase)}`
152
+ )
153
+ }
94
154
  }
95
155
  }
96
156
  return { catalogs, phrases }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexibase/rdx-next",
3
- "version": "6.0.0",
3
+ "version": "6.0.1",
4
4
  "description": "The Next.js runtime for RDX documentation projects.",
5
5
  "license": "MIT",
6
6
  "type": "module",