Skip to content

Commit f0c45f5

Browse files
committed
Sync option descriptions -> compiler
1 parent f001281 commit f0c45f5

File tree

5 files changed

+741
-0
lines changed

5 files changed

+741
-0
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
name: Sync option descriptions -> compiler
2+
on:
3+
# When our dependencies change
4+
push:
5+
paths:
6+
- "**/package.json"
7+
- .github/workflows/sync-option-descriptions.yml
8+
- packages/tsconfig-reference/scripts/sync-option-descriptions.ts
9+
- packages/tsconfig-reference/tsconfig.json
10+
schedule:
11+
# https://crontab.guru/#0_0_*_*_0
12+
- cron: 0 0 * * 0
13+
workflow_dispatch:
14+
jobs:
15+
sync-option-descriptions:
16+
#if: github.repository == 'microsoft/TypeScript-Website'
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v2
20+
- uses: actions/checkout@v2
21+
with:
22+
repository: microsoft/TypeScript
23+
#token: ${{ secrets.GITHUB_BOT_TOKEN }}
24+
token: ${{ secrets.COMPILER_TOKEN }}
25+
path: TypeScript
26+
- run: git switch --create sync-option-descriptions
27+
working-directory: TypeScript
28+
- uses: actions/setup-node@v2
29+
with:
30+
cache: yarn
31+
- run: yarn install
32+
- run: tsc --build packages/tsconfig-reference
33+
- name: Update option descriptions in the compiler
34+
run: node packages/tsconfig-reference/scripts/sync-option-descriptions
35+
- run: git config user.name "$GITHUB_REPOSITORY:.github/workflows/sync-option-descriptions.yml"
36+
working-directory: TypeScript
37+
- run: git config user.email [email protected]
38+
working-directory: TypeScript
39+
- run: |
40+
git commit --all \
41+
--message "🤖 Sync option descriptions <- $GITHUB_REPOSITORY@$GITHUB_SHA" \
42+
--message "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
43+
working-directory: TypeScript
44+
- name: Push if anything changed
45+
run: |
46+
# Get the first three existing commits: Existing sync commit,
47+
# parent and baselines commit, possibly. Ignore the first line
48+
# of git cherry output: The current tip of main. Might be
49+
# equivalent (-) or not (+) due to shallow cloning.
50+
git fetch --depth 3 origin sync-option-descriptions &&
51+
! git cherry origin/sync-option-descriptions | tail --lines 1 | grep --invert-match --quiet ^- ||
52+
git push --force-with-lease origin sync-option-descriptions
53+
working-directory: TypeScript
54+
- name: Update baselines
55+
run: gh workflow run --ref sync-option-descriptions accept-baselines-fix-lints.yaml
56+
working-directory: TypeScript
57+
env:
58+
#GITHUB_TOKEN: ${{ secrets.GITHUB_BOT_TOKEN }}
59+
GITHUB_TOKEN: ${{ secrets.COMPILER_TOKEN }}
60+
- name: Submit pull request
61+
run: |
62+
gh pr create \
63+
--repo microsoft/TypeScript \
64+
--title '🤖 Sync option descriptions <- website' \
65+
--body 'Get command-line option descriptions from the website Markdown and update their corresponding descriptions in the compiler.
66+
67+
/cc @orta' || true
68+
working-directory: TypeScript
69+
env:
70+
#GITHUB_TOKEN: ${{ secrets.GITHUB_BOT_TOKEN }}
71+
GITHUB_TOKEN: ${{ secrets.COMPILER_TOKEN }}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@oss-docs/sync": "^1.1.4",
5151
"@types/express": "^4.17.6",
5252
"gray-matter": "4.0.2",
53+
"mdast-util-to-markdown": "^1.2.4",
5354
"node-polyfill-webpack-plugin": "^1.1.0",
5455
"serve-handler": "^6.1.2",
5556
"xmldom": "^0.5.0"

packages/tsconfig-reference/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
"semi": true
2424
},
2525
"devDependencies": {
26+
"mdast-util-from-markdown": "^1.1.0",
27+
"mdast-util-frontmatter": "^1.0.0",
2628
"remark": "^11.0.2",
2729
"remark-html": "^10.0.0",
2830
"ts-node": "*"
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import * as fs from "fs";
2+
import jsYaml from "js-yaml";
3+
import { fromMarkdown } from "mdast-util-from-markdown";
4+
import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
5+
import { frontmatter } from "micromark-extension-frontmatter";
6+
import ts from "typescript";
7+
import type * as unist from "unist";
8+
9+
// commandLineParser.ts maps option names -> current (old) diagnostic
10+
// names. Lookup new descriptions in website Markdown and rewrite
11+
// diagnostic names accordingly.
12+
const commandLineParserFileName =
13+
"TypeScript/src/compiler/commandLineParser.ts";
14+
const commandLineParserText = String(
15+
fs.readFileSync(commandLineParserFileName)
16+
);
17+
const sourceFile = ts.createSourceFile(
18+
commandLineParserFileName,
19+
commandLineParserText,
20+
0
21+
);
22+
const newMessages: { [oldName: string]: string } = {};
23+
ts.forEachChild(sourceFile, visit);
24+
fs.writeFileSync(
25+
commandLineParserFileName,
26+
commandLineParserText.replace(
27+
new RegExp(String.raw`\b(?:${Object.keys(newMessages).join("|")})\b`, "g"),
28+
(oldName) => convertPropertyName(newMessages[oldName])
29+
)
30+
);
31+
32+
// Replace old messages with new
33+
Object.assign(
34+
newMessages,
35+
Object.fromEntries(
36+
Object.values(newMessages).map((newMessage) => [
37+
convertPropertyName(newMessage),
38+
newMessage,
39+
])
40+
)
41+
);
42+
const diagnosticMessagesFileName =
43+
"TypeScript/src/compiler/diagnosticMessages.json";
44+
const diagnosticMessagesText = String(
45+
fs.readFileSync(diagnosticMessagesFileName)
46+
);
47+
const diagnosticMessages = JSON.parse(diagnosticMessagesText);
48+
const oldNames = new Set(
49+
Object.keys(diagnosticMessages).map((oldMessage) =>
50+
convertPropertyName(oldMessage)
51+
)
52+
);
53+
fs.writeFileSync(
54+
diagnosticMessagesFileName,
55+
(
56+
JSON.stringify(
57+
Object.fromEntries(
58+
Object.entries(diagnosticMessages)
59+
.map(([oldMessage, data]) => {
60+
const oldName = convertPropertyName(oldMessage);
61+
const newMessage = newMessages[oldName];
62+
const newName = newMessage && convertPropertyName(newMessage);
63+
// Careful not to insert duplicates
64+
return (
65+
(newName === oldName || !oldNames.has(newName)) && [
66+
newMessage || oldMessage,
67+
data,
68+
]
69+
);
70+
})
71+
.filter(
72+
(entry): entry is Exclude<typeof entry, false> => entry as never
73+
)
74+
),
75+
undefined,
76+
4
77+
) + "\n"
78+
).replace(/\n/g, "\r\n") //.replaceAll("\n", "\r\n")
79+
);
80+
81+
// Visit nodes like the following:
82+
// {
83+
// name: "optionName",
84+
// description: Diagnostics.Voila_la_description
85+
// }
86+
function visit(node: ts.Node) {
87+
if (!ts.isObjectLiteralExpression(node)) {
88+
ts.forEachChild(node, visit);
89+
return;
90+
}
91+
const name = getPropertyAssignment(node, "name");
92+
if (!name || !ts.isStringLiteral(name.initializer)) return;
93+
const description = getPropertyAssignment(node, "description");
94+
if (
95+
!description ||
96+
!ts.isPropertyAccessExpression(description.initializer) ||
97+
!ts.isIdentifier(description.initializer.expression) ||
98+
description.initializer.expression.text !== "Diagnostics"
99+
)
100+
return;
101+
const optionName = name.initializer.text;
102+
const oldName = description.initializer.name.text;
103+
const newMessage = getDescription(optionName);
104+
if (!newMessage) return;
105+
newMessages[oldName] = newMessage;
106+
}
107+
108+
function getPropertyAssignment(obj: ts.ObjectLiteralExpression, name: string) {
109+
return obj.properties.find(
110+
(property): property is ts.PropertyAssignment =>
111+
ts.isPropertyAssignment(property) &&
112+
ts.isIdentifier(property.name) &&
113+
property.name.text === name
114+
);
115+
}
116+
117+
// Get new description from website Markdown
118+
function getDescription(optionName: string) {
119+
const fileName = new URL(
120+
`../copy/en/options/${optionName}.md`,
121+
import.meta.url
122+
);
123+
if (!fs.existsSync(fileName)) return;
124+
const doc = fs.readFileSync(fileName);
125+
const optionTree = fromMarkdown(doc, {
126+
extensions: [frontmatter()],
127+
mdastExtensions: [frontmatterFromMarkdown()],
128+
});
129+
// Get frontmatter
130+
const yaml = optionTree.children.find(
131+
(yaml): yaml is typeof yaml & { type: "yaml" } => yaml.type === "yaml"
132+
);
133+
if (!yaml) return;
134+
const data = jsYaml.load(yaml.value) as { oneline: string };
135+
// Strip Markdown
136+
const descriptionTree = fromMarkdown(data.oneline);
137+
return toString(descriptionTree);
138+
}
139+
140+
function toString(node: Partial<unist.Parent>): string {
141+
return node.type === "inlineCode"
142+
? `'${node.value}'`
143+
: (node.value as string) ||
144+
node.children?.map((child) => toString(child)).join("") ||
145+
"";
146+
}
147+
148+
// https://github.com/microsoft/TypeScript/blob/e00b5ecd406b3d299ca69ef6780cc22ef0ecef4a/scripts/processDiagnosticMessages.ts#L106-L124
149+
function convertPropertyName(origName: string): string {
150+
let result = origName
151+
.split("")
152+
.map((char) => {
153+
if (char === "*") return "_Asterisk";
154+
if (char === "/") return "_Slash";
155+
if (char === ":") return "_Colon";
156+
return /\w/.test(char) ? char : "_";
157+
})
158+
.join("");
159+
160+
// get rid of all multi-underscores
161+
result = result.replace(/_+/g, "_");
162+
163+
// remove any leading underscore, unless it is followed by a number.
164+
result = result.replace(/^_([^\d])/, "$1");
165+
166+
// get rid of all trailing underscores.
167+
result = result.replace(/_$/, "");
168+
169+
return result;
170+
}

0 commit comments

Comments
 (0)