-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
i18n: use tsc to collect UIStrings #9487
Conversation
the changes to |
} | ||
|
||
// @ts-ignore regex just matched |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rearranged the ifs above to avoid this
createPsuedoLocaleStrings, | ||
convertMessageToPlaceholders: convertMessageToCtc, | ||
convertMessageToCtc, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it didn't make sense to rename this just for exporting for tests, but missed in the review for #9114 :)
// @ts-ignore - esprima's type definition is supremely lacking | ||
const ast = esprima.parse(justUIStrings, {comment: true, range: true}); | ||
|
||
for (const stmt of ast.body) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's only ever one stmt
(if we did our extraction correctly), so no need to loop :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it! Looks pretty good, I think the dependency change for me mentally moves this script officially from rag-tag hacky solution to realistic code we support 😄
|
||
// @ts-ignore - Not part of the public tsc interface yet. | ||
const jsDocComments = tsc.getJSDocCommentsAndTags(property); | ||
if (jsDocComments.length === 0) throw new Error(`No Description for message "${message}"`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we get rid of the message inside computeDescription
then and make this the "Missing description comment" one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we get rid of the message inside
computeDescription
then and make this the "Missing description comment" one?
hmm, do you mean like check that either ast.comment
or ast.tags
is defined here? Or output an empty description
from computeDescription
and throw here if it's the empty string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I meant this is a duplicate case and error message of the fallback in computeDescription
, could computeDescription
accept ast|undefined
and do the check there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could
computeDescription
acceptast|undefined
and do the check there?
got it
Kinda crazy that esprima has more than double weekly downloads than typescript still o.O |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love it! 👋 esprima.
for (const tag of ast.tags) { | ||
const comment = coerceToSingleLineAndTrim(tag.comment); | ||
|
||
if (tag.tagName.text === 'description') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this throw if we encounter a non-standard tag?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this throw if we encounter a non-standard tag?
I think most of the time we don't care, and if someone really wanted to add a different tag to communicate something else, they could. I could see someone in a plugin adding an @const
to the strings and then wanting us to still support them with collect-strings
. It seems like a corner case, but otoh if someone misspells @description
or something it will be picked up by the missing description error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair. I'm not sure I'm convinced, but I guess there isn't much harm in letting new tags roam free.
* @return {{placeholderName: string, exampleValue: string}} | ||
*/ | ||
function parseAtExample(rawExample) { | ||
const match = rawExample.match(/^{(?<exampleValue>[^}]+)} (?<placeholderName>.+)$/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, at this point, the raw example is in the form "{exampleValue} placeholderName"? The @
tag is gone? Seems like this wouldn't be the case based on the function description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, at this point, the raw example is in the form "{exampleValue} placeholderName"? The
@
tag is gone? Seems like this wouldn't be the case based on the function description.
good call. I reworded to make it clearer what it was parsing, but also left the @example
context so it's not pretending to not be used for that specific purpose.
const liveUIStrings = evalJustUIStrings(justUIStrings); | ||
|
||
expect(() => collect.parseUIStrings(justUIStrings, liveUIStrings)) | ||
.toThrow(/Incorrectly formatted @example/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it needs more context around it, what message was bad, or what @example text it was iterating over. This would help immediately know what is wrong.
good point. added example string to error message
|
||
if (regexMatches && !exportsUIStrings) { | ||
throw new Error('UIStrings defined but not exported'); | ||
if (!regexMatch) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dunno if it's worth it, but can this be a XOR then a ternary on which error to throw? Ignore if it's pointless, but this kind of boolean logic is deceptively simple, but yet hard to follow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dunno if it's worth it, but can this be a XOR then a ternary on which error to throw? Ignore if it's pointless, but this kind of boolean logic is deceptively simple, but yet hard to follow.
that sounds more complicated, but I added a comment and an error message tweak to try to make it clearer :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
const {placeholderName, exampleValue} = parseExampleJsDoc(comment); | ||
examples[placeholderName] = exampleValue; | ||
} else { | ||
throw new Error('bad tag' + tag.tagName.text); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw new Error('bad tag' + tag.tagName.text); | |
throw new Error(`Unexpected tagName "@${tag.tagName.text}"`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, whoops, I threw that in there to see how hard it was to accidentally trigger that case with reasonable typos and then I guess accidentally committed it. Maybe we'll go with throwing in that case after all :)
*/ | ||
function coerceToSingleLineAndTrim(comment = '') { | ||
// Line breaks within a jsdoc comment should always be replaceable with a space. | ||
return comment.replace(/\n/g, ' ').trim(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I tend to go with /\n+/g, ' '
if i'm turning into single line in other contexts, not sure if this is different
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I tend to go with
/\n+/g, ' '
if i'm turning into single line in other contexts, not sure if this is different
it might be stretching interpreting a string with multiple line breaks in it as still the same jsdoc string but I think it's the only thing that makes sense here, so, sounds good :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, my nits were addressed
moves from using esprima to the typescript compiler to extract UIStrings from our files. Discussed this with @exterkamp ahead of time, though this PR grew a little in the telling. Main benefits:
@
tags in separate vars, eliminating one parse step and having to keep track of comment start/end positions*
and starting whitespace from jsdoc strings, joins across newlines, etcalso reorganized a bit so the parsing/ast walking is split off in a separate function, allowing testing to be a bit easier (and laying the groundwork for exposing as a utility for third-party plugins, etc, to use if they wish)