@lit/localize-tools
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -10,6 +10,46 @@ # Changelog | ||
## [0.2.0] - 2021-03-30 | ||
### Changed | ||
- **[BREAKING]** Description comments (`// msgdesc:`) have been removed in favor | ||
of the `desc` option. | ||
Before: | ||
```js | ||
// msgdesc: Home page | ||
class HomePage { | ||
hello() { | ||
// msgdesc: Greeting to Earth | ||
return msg(html`Hello World`); | ||
} | ||
goodbye() { | ||
// msgdesc: Farewell to Earth | ||
return msg(html`Goodbye World`); | ||
} | ||
} | ||
``` | ||
After: | ||
```js | ||
class HomePage { | ||
hello() { | ||
return msg(html`Hello World`, { | ||
desc: 'Home page / Greeting to Earth', | ||
}); | ||
} | ||
goodbye() { | ||
return msg(html`Goodbye World`, { | ||
desc: 'Home page / Farewell to Earth', | ||
}); | ||
} | ||
} | ||
``` | ||
## [0.1.1] - 2021-03-30 | ||
### Changed | ||
- Bumped dependency versions for `xmldom` and `@lit/localize` | ||
@@ -16,0 +56,0 @@ |
@@ -96,7 +96,7 @@ /** | ||
bundle.appendChild(messagesNode); | ||
for (const { name, contents, descStack } of sourceMessages) { | ||
for (const { name, contents, desc } of sourceMessages) { | ||
const messageNode = doc.createElement('msg'); | ||
messageNode.setAttribute('name', name); | ||
if (descStack.length > 0) { | ||
messageNode.setAttribute('desc', descStack.join(' / ')); | ||
if (desc) { | ||
messageNode.setAttribute('desc', desc); | ||
} | ||
@@ -103,0 +103,0 @@ indent(messagesNode, 2); |
@@ -160,3 +160,3 @@ /** | ||
indent(body); | ||
for (const { name, contents: sourceContents, descStack } of sourceMessages) { | ||
for (const { name, contents: sourceContents, desc } of sourceMessages) { | ||
// https://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#trans-unit | ||
@@ -167,6 +167,6 @@ const transUnit = doc.createElement('trans-unit'); | ||
transUnit.setAttribute('id', name); | ||
if (descStack.length > 0) { | ||
if (desc) { | ||
// https://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#note | ||
const note = doc.createElement('note'); | ||
note.appendChild(doc.createTextNode(descStack.join(' / '))); | ||
note.appendChild(doc.createTextNode(desc)); | ||
transUnit.appendChild(note); | ||
@@ -173,0 +173,0 @@ indent(transUnit, 1); |
@@ -38,6 +38,5 @@ /** | ||
/** | ||
* The stack of "msgdesc:" comments at the point where this message was | ||
* extracted. Used for generating "desc" attributes in our XLB file. | ||
* Description for this message. | ||
*/ | ||
descStack: string[]; | ||
desc: string | undefined; | ||
/** | ||
@@ -44,0 +43,0 @@ * True if this message was tagged as a lit-html template, or was a function |
@@ -25,7 +25,4 @@ /** | ||
return messages.sort((a, b) => { | ||
const descCompare = a.descStack | ||
.join('') | ||
.localeCompare(b.descStack.join('')); | ||
if (descCompare !== 0) { | ||
return descCompare; | ||
if (a.desc ?? '' !== b.desc ?? '') { | ||
return (a.desc ?? '').localeCompare(b.desc ?? ''); | ||
} | ||
@@ -32,0 +29,0 @@ return a.file.fileName.localeCompare(b.file.fileName); |
@@ -27,3 +27,3 @@ /** | ||
id?: string; | ||
args?: ts.NodeArray<ts.Expression>; | ||
desc?: string; | ||
}, ts.Diagnostic>; | ||
@@ -30,0 +30,0 @@ interface ExtractedTemplate { |
@@ -17,3 +17,3 @@ /** | ||
for (const sourcefile of program.getSourceFiles()) { | ||
extractMessagesFromNode(sourcefile, program, sourcefile, messages, errors, []); | ||
extractMessagesFromNode(sourcefile, program, sourcefile, messages, errors); | ||
} | ||
@@ -33,12 +33,4 @@ const deduped = dedupeMessages(messages); | ||
*/ | ||
function extractMessagesFromNode(file, program, node, messages, errors, descStack) { | ||
const newDescs = extractMsgDescs(node, file.getFullText()); | ||
if (newDescs.length > 0) { | ||
// Note we just make a copy of the stack each time we get a new description. | ||
// This is a little simpler than modifying it in-place given that we have to | ||
// de-duplicate. This could be a spot to optimize if we start handling | ||
// humongous applications. | ||
descStack = dedupeMsgDescs([...descStack, ...newDescs]); | ||
} | ||
const extractResult = extractMsg(file, program, node, descStack); | ||
function extractMessagesFromNode(file, program, node, messages, errors) { | ||
const extractResult = extractMsg(file, program, node); | ||
if (extractResult.error) { | ||
@@ -51,3 +43,3 @@ errors.push(extractResult.error); | ||
ts.forEachChild(node, (node) => { | ||
extractMessagesFromNode(file, program, node, messages, errors, descStack); | ||
extractMessagesFromNode(file, program, node, messages, errors); | ||
}); | ||
@@ -59,3 +51,3 @@ } | ||
*/ | ||
function extractMsg(file, program, node, descStack) { | ||
function extractMsg(file, program, node) { | ||
if (!isMsgCall(node, program.getTypeChecker())) { | ||
@@ -89,3 +81,5 @@ // We're not interested. | ||
isLitTemplate, | ||
descStack: descStack.map((desc) => desc.text), | ||
// Note we pass node.parent because node is a CallExpression node, but the | ||
// JSDoc tag will be attached to the parent Expression node. | ||
desc: options.desc, | ||
}, | ||
@@ -106,4 +100,4 @@ }; | ||
} | ||
let id = undefined; | ||
let args = undefined; | ||
let id; | ||
let desc; | ||
for (const property of node.properties) { | ||
@@ -141,3 +135,3 @@ // { | ||
return { | ||
error: createDiagnostic(file, property.initializer, `Options id property must be a non-empty string literal`), | ||
error: createDiagnostic(file, property.initializer, `msg id option must be a non-empty string with no expressions`), | ||
}; | ||
@@ -147,17 +141,18 @@ } | ||
} | ||
else if (name === 'args') { | ||
if (!ts.isArrayLiteralExpression(property.initializer)) { | ||
else if (name === 'desc') { | ||
if (!ts.isStringLiteral(property.initializer) && | ||
!ts.isNoSubstitutionTemplateLiteral(property.initializer)) { | ||
return { | ||
error: createDiagnostic(file, property.initializer, `Options args property must be an array literal`), | ||
error: createDiagnostic(file, property.initializer, `msg desc option must be a string with no expressions`), | ||
}; | ||
} | ||
args = property.initializer.elements; | ||
desc = property.initializer.text; | ||
} | ||
else { | ||
return { | ||
error: createDiagnostic(file, property, `Options object property must be "id" or "args"`), | ||
error: createDiagnostic(file, property, `Invalid msg option. Supported: id, desc`), | ||
}; | ||
} | ||
} | ||
return { result: { id, args } }; | ||
return { result: { id, desc } }; | ||
} | ||
@@ -379,40 +374,2 @@ /** | ||
} | ||
/** | ||
* Look for "// msgdesc: foo" comments attached to the given node. | ||
*/ | ||
function extractMsgDescs(node, fileText) { | ||
const ranges = ts.getLeadingCommentRanges(fileText, node.getFullStart()); | ||
const descs = []; | ||
if (ranges !== undefined) { | ||
for (const range of ranges) { | ||
const comment = fileText.slice(range.pos, range.end); | ||
const match = comment.match(/.*msgdesc:\s*(.+)/); | ||
if (match !== null) { | ||
descs.push({ pos: range.pos, end: range.end, text: match[1].trim() }); | ||
} | ||
} | ||
} | ||
return descs; | ||
} | ||
/** | ||
* The way TypeScript treats comments in the AST means that it's possible for | ||
* us to extract the exact same exact comment range multiple times when | ||
* traversing certain AST structures (e.g. both for an "expression" node and | ||
* some child node it has). De-duplicate these based on their source text | ||
* positions. | ||
*/ | ||
function dedupeMsgDescs(descs) { | ||
// Since Maps preserve order, we can just pick a key that will be the same for | ||
// duplicate comment ranges, populate the map, and then iterate through the | ||
// values. | ||
const map = new Map(); | ||
for (const desc of descs) { | ||
const key = `${desc.pos}:${desc.end}`; | ||
if (map.has(key)) { | ||
continue; | ||
} | ||
map.set(key, desc); | ||
} | ||
return [...map.values()]; | ||
} | ||
function replaceHtmlWithPlaceholders(html) { | ||
@@ -419,0 +376,0 @@ const components = []; |
{ | ||
"name": "@lit/localize-tools", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"publishConfig": { | ||
@@ -40,3 +40,3 @@ "access": "public" | ||
"dependencies": { | ||
"@lit/localize": "^0.8.0", | ||
"@lit/localize": "^0.9.0", | ||
"fs-extra": "^9.0.0", | ||
@@ -43,0 +43,0 @@ "glob": "^7.1.6", |
@@ -122,7 +122,7 @@ /** | ||
bundle.appendChild(messagesNode); | ||
for (const {name, contents, descStack} of sourceMessages) { | ||
for (const {name, contents, desc} of sourceMessages) { | ||
const messageNode = doc.createElement('msg'); | ||
messageNode.setAttribute('name', name); | ||
if (descStack.length > 0) { | ||
messageNode.setAttribute('desc', descStack.join(' / ')); | ||
if (desc) { | ||
messageNode.setAttribute('desc', desc); | ||
} | ||
@@ -129,0 +129,0 @@ indent(messagesNode, 2); |
@@ -223,3 +223,3 @@ /** | ||
for (const {name, contents: sourceContents, descStack} of sourceMessages) { | ||
for (const {name, contents: sourceContents, desc} of sourceMessages) { | ||
// https://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#trans-unit | ||
@@ -231,6 +231,6 @@ const transUnit = doc.createElement('trans-unit'); | ||
if (descStack.length > 0) { | ||
if (desc) { | ||
// https://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#note | ||
const note = doc.createElement('note'); | ||
note.appendChild(doc.createTextNode(descStack.join(' / '))); | ||
note.appendChild(doc.createTextNode(desc)); | ||
transUnit.appendChild(note); | ||
@@ -237,0 +237,0 @@ indent(transUnit, 1); |
@@ -44,6 +44,5 @@ /** | ||
/** | ||
* The stack of "msgdesc:" comments at the point where this message was | ||
* extracted. Used for generating "desc" attributes in our XLB file. | ||
* Description for this message. | ||
*/ | ||
descStack: string[]; | ||
desc: string | undefined; | ||
@@ -103,7 +102,4 @@ /** | ||
return messages.sort((a, b) => { | ||
const descCompare = a.descStack | ||
.join('') | ||
.localeCompare(b.descStack.join('')); | ||
if (descCompare !== 0) { | ||
return descCompare; | ||
if (a.desc ?? '' !== b.desc ?? '') { | ||
return (a.desc ?? '').localeCompare(b.desc ?? ''); | ||
} | ||
@@ -110,0 +106,0 @@ return a.file.fileName.localeCompare(b.file.fileName); |
@@ -26,10 +26,3 @@ /** | ||
for (const sourcefile of program.getSourceFiles()) { | ||
extractMessagesFromNode( | ||
sourcefile, | ||
program, | ||
sourcefile, | ||
messages, | ||
errors, | ||
[] | ||
); | ||
extractMessagesFromNode(sourcefile, program, sourcefile, messages, errors); | ||
} | ||
@@ -55,15 +48,5 @@ const deduped = dedupeMessages(messages); | ||
messages: ProgramMessage[], | ||
errors: ts.Diagnostic[], | ||
descStack: MsgDesc[] | ||
errors: ts.Diagnostic[] | ||
): void { | ||
const newDescs = extractMsgDescs(node, file.getFullText()); | ||
if (newDescs.length > 0) { | ||
// Note we just make a copy of the stack each time we get a new description. | ||
// This is a little simpler than modifying it in-place given that we have to | ||
// de-duplicate. This could be a spot to optimize if we start handling | ||
// humongous applications. | ||
descStack = dedupeMsgDescs([...descStack, ...newDescs]); | ||
} | ||
const extractResult = extractMsg(file, program, node, descStack); | ||
const extractResult = extractMsg(file, program, node); | ||
if (extractResult.error) { | ||
@@ -76,3 +59,3 @@ errors.push(extractResult.error); | ||
ts.forEachChild(node, (node) => { | ||
extractMessagesFromNode(file, program, node, messages, errors, descStack); | ||
extractMessagesFromNode(file, program, node, messages, errors); | ||
}); | ||
@@ -88,4 +71,3 @@ } | ||
program: ts.Program, | ||
node: ts.Node, | ||
descStack: MsgDesc[] | ||
node: ts.Node | ||
): ResultOrError<ProgramMessage | undefined, ts.Diagnostic> { | ||
@@ -131,3 +113,5 @@ if (!isMsgCall(node, program.getTypeChecker())) { | ||
isLitTemplate, | ||
descStack: descStack.map((desc) => desc.text), | ||
// Note we pass node.parent because node is a CallExpression node, but the | ||
// JSDoc tag will be attached to the parent Expression node. | ||
desc: options.desc, | ||
}, | ||
@@ -143,6 +127,3 @@ }; | ||
file: ts.SourceFile | ||
): ResultOrError< | ||
{id?: string; args?: ts.NodeArray<ts.Expression>}, | ||
ts.Diagnostic | ||
> { | ||
): ResultOrError<{id?: string; desc?: string}, ts.Diagnostic> { | ||
if (node === undefined) { | ||
@@ -161,4 +142,4 @@ return {result: {}}; | ||
let id: string | undefined = undefined; | ||
let args: ts.NodeArray<ts.Expression> | undefined = undefined; | ||
let id: string | undefined; | ||
let desc: string | undefined; | ||
@@ -211,3 +192,3 @@ for (const property of node.properties) { | ||
property.initializer, | ||
`Options id property must be a non-empty string literal` | ||
`msg id option must be a non-empty string with no expressions` | ||
), | ||
@@ -217,4 +198,7 @@ }; | ||
id = property.initializer.text; | ||
} else if (name === 'args') { | ||
if (!ts.isArrayLiteralExpression(property.initializer)) { | ||
} else if (name === 'desc') { | ||
if ( | ||
!ts.isStringLiteral(property.initializer) && | ||
!ts.isNoSubstitutionTemplateLiteral(property.initializer) | ||
) { | ||
return { | ||
@@ -224,7 +208,7 @@ error: createDiagnostic( | ||
property.initializer, | ||
`Options args property must be an array literal` | ||
`msg desc option must be a string with no expressions` | ||
), | ||
}; | ||
} | ||
args = property.initializer.elements; | ||
desc = property.initializer.text; | ||
} else { | ||
@@ -235,3 +219,3 @@ return { | ||
property, | ||
`Options object property must be "id" or "args"` | ||
`Invalid msg option. Supported: id, desc` | ||
), | ||
@@ -242,3 +226,3 @@ }; | ||
return {result: {id, args}}; | ||
return {result: {id, desc}}; | ||
} | ||
@@ -510,55 +494,2 @@ | ||
/** | ||
* A message description extracted from a TypeScript comment in a particular | ||
* file. | ||
*/ | ||
interface MsgDesc { | ||
/** Where the comment begins in the source text. */ | ||
pos: number; | ||
/** Where the comment ends in the source text. */ | ||
end: number; | ||
/** The extracted description (the part after `// msgdesc: `) */ | ||
text: string; | ||
} | ||
/** | ||
* Look for "// msgdesc: foo" comments attached to the given node. | ||
*/ | ||
function extractMsgDescs(node: ts.Node, fileText: string): MsgDesc[] { | ||
const ranges = ts.getLeadingCommentRanges(fileText, node.getFullStart()); | ||
const descs: MsgDesc[] = []; | ||
if (ranges !== undefined) { | ||
for (const range of ranges) { | ||
const comment = fileText.slice(range.pos, range.end); | ||
const match = comment.match(/.*msgdesc:\s*(.+)/); | ||
if (match !== null) { | ||
descs.push({pos: range.pos, end: range.end, text: match[1].trim()}); | ||
} | ||
} | ||
} | ||
return descs; | ||
} | ||
/** | ||
* The way TypeScript treats comments in the AST means that it's possible for | ||
* us to extract the exact same exact comment range multiple times when | ||
* traversing certain AST structures (e.g. both for an "expression" node and | ||
* some child node it has). De-duplicate these based on their source text | ||
* positions. | ||
*/ | ||
function dedupeMsgDescs(descs: MsgDesc[]): MsgDesc[] { | ||
// Since Maps preserve order, we can just pick a key that will be the same for | ||
// duplicate comment ranges, populate the map, and then iterate through the | ||
// values. | ||
const map = new Map<string, MsgDesc>(); | ||
for (const desc of descs) { | ||
const key = `${desc.pos}:${desc.end}`; | ||
if (map.has(key)) { | ||
continue; | ||
} | ||
map.set(key, desc); | ||
} | ||
return [...map.values()]; | ||
} | ||
function replaceHtmlWithPlaceholders( | ||
@@ -565,0 +496,0 @@ html: string |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
273683
5913
+ Added@lit/localize@0.9.0(transitive)
- Removed@lit/localize@0.8.0(transitive)
Updated@lit/localize@^0.9.0