mirror of
https://github.com/wassname/talk.git
synced 2026-06-28 04:07:52 +08:00
c9a0ab8848
* chore: upgrade fluent * chore: upgrade metascraper * chore: upgrade akismet-api * chore: upgrade apollo-server-express * chore: upgrade archiver * chore: upgrade bull * chore: upgrade express, cheerio, content-security-policy-builder * chore: upgrade convict * chore: upgrade cors, cron * chore: upgrade csv-stringify * chore: upgrade dompurify * chore: upgrade dotenv * chore: upgrade express-static-gzip * chore: upgrade fs-extra * chore: upgrade graphql-js * chore: upgrade graphql packages * chore: upgrade html-minifier * chore: upgrade html-to-text * chore: upgrade ioredis * chore: upgrade joi * chore: upgrade jsdom * chore: upgrade jsonwebtoken * chore: upgrade juice * chore: upgrade jwks-rsa ad linkifyjs * chore: upgrade lodash * chore: upgrade luxon * chore: upgrade metascraper * chore: upgrade mongodb * chore: upgrade ms * chore: upgrade node and node-fetch types * chore: upgrade nodemailer nunjucks and typescript-eslint * chore: Upgrade passport * upgrade: prom-client react-helmet source-map-support stack-utils * chore: upgrade uuid * chore: upgrade @babel packages * chore: upgrade types * chore: upgrade autoprefixer * chore: upgrade jest * chore: upgrade ts-jest * chore: remove linkify.d.ts * chore: upgrade bowser * chore: case-sensitive-paths-webpack-plugin * chore: upgrade classnames * chore: upgrade commander * chore: upgrade comment-json * chore: upgrade cross-spawn compression-webpack-plugin del * chore: upgrade build and watch related dependencies * chore: upgrade css-vars-ponyfill * chore: upgrade eslint and css-vars-ponyfill * chore: upgrade enzyme and eventemitter2 * fix: form bug * chore: upgrade farce * chore: upgrade final form * chore: upgrade react-popper * chore: upgrade flat and fork-ts-checker-webpack-plugin * chore: upgrade husky and gulp related, intersection observer * chore: upgrade lint-staged * chore: upgrade marked, loader-utils, mini-css-extract-plugin * chore: upgrade postcss-nested, proxy-polyfill, pstree.remy * chore: upgrade prettier * chore: fix prettier changes, upgrade react * chore: mute createFactory deprecated message * chore: upgrade react-copy-to-clipbard, react-axe, react-dom, react-test-renderer, react-timeago * chore: upgrade react-transistion-group, react-responsive * chore: upgrade types * chore: upgrade react-dev-utils, react-error-overlay regenerator-runtime * chore: upgrade types, sinon, sockjs-client, strip-ansi * chore: upgrade types, fonts * chore: upgrade nunjucks, ts-node, typescript-snapshot-plugin, wait-for-expect * chore: upgrade eslint packages * chore: upgrade fluent, types * chore: upgrade jsdom dep * chore: upgrade mongo * chore: upgrade deps * chore: upgrade typescript, recompose * chore: upgrade prettier * chore: remove obsolete prettier config * chore: upgrade jsdom types * chore: upgrade typescript-eslint * chore: upgrad deps * chore: upgrade deps * chore: upgrade relay related modules * chore: upgrade docz WIP * chore: upgrade docz * chore: add guard * chore: remove obsolete line * chore: comment * chore: refactors * fix: hook count change error
278 lines
7.5 KiB
TypeScript
278 lines
7.5 KiB
TypeScript
/* eslint-disable no-bitwise */
|
|
|
|
import { codeBlock, stripIndent } from "common-tags";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import ts from "typescript";
|
|
|
|
interface DocEntry {
|
|
name: string;
|
|
docs?: string;
|
|
type: "ViewerNetworkEvent" | "ViewerEvent";
|
|
text?: string;
|
|
}
|
|
|
|
/**
|
|
* We use this regexp to find a previous block that we
|
|
* are going to update in the readme file.
|
|
*/
|
|
const BLOCK_REGEXP = /<!-- START docs:events -->(.|\n)*<!-- END docs:events -->/gm;
|
|
|
|
/** Build flags that affects AST generation */
|
|
const buildFlags =
|
|
// Do not truncate output.
|
|
ts.NodeBuilderFlags.NoTruncation |
|
|
// Use multiline object literals format.
|
|
ts.NodeBuilderFlags.MultilineObjectLiterals;
|
|
|
|
/** Generate documentation for all classes in a set of .ts files */
|
|
function gatherEntries(
|
|
fileNames: string[],
|
|
options: ts.CompilerOptions
|
|
): DocEntry[] {
|
|
// Build a program using the set of root file names in fileNames
|
|
const program = ts.createProgram(fileNames, options);
|
|
|
|
const printer = ts.createPrinter({
|
|
noEmitHelpers: true,
|
|
omitTrailingSemicolon: true,
|
|
removeComments: false,
|
|
});
|
|
|
|
// Get the checker, we will use it to find more about classes
|
|
const checker = program.getTypeChecker();
|
|
|
|
const data: DocEntry[] = [];
|
|
|
|
/** Hold a pointer to the sourcefile we are currently processing. */
|
|
let currentSourceFile: ts.SourceFile;
|
|
|
|
// Visit every sourceFile in the program
|
|
for (const sourceFile of program.getSourceFiles()) {
|
|
if (!sourceFile.isDeclarationFile) {
|
|
currentSourceFile = sourceFile;
|
|
// Walk the tree to search for classes
|
|
ts.forEachChild(sourceFile, visit);
|
|
}
|
|
}
|
|
|
|
const sorted = data.sort((a, b) => {
|
|
if (a.name > b.name) {
|
|
return 1;
|
|
}
|
|
if (b.name > a.name) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
return sorted;
|
|
|
|
/** visit nodes finding exported events */
|
|
function visit(node: ts.Node) {
|
|
// Only consider exported nodes
|
|
if (!isNodeExported(node)) {
|
|
return;
|
|
}
|
|
|
|
if (ts.isVariableStatement(node)) {
|
|
if (
|
|
!node.getFullText().includes("createViewerNetworkEvent") &&
|
|
!node.getFullText().includes("createViewerEvent")
|
|
) {
|
|
return;
|
|
}
|
|
const firstChild = node.declarationList.declarations[0];
|
|
if (ts.isVariableDeclaration(firstChild)) {
|
|
const symbol = checker.getSymbolAtLocation(firstChild.name);
|
|
if (symbol) {
|
|
serializeEventSymbol(symbol);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function serializeEventSymbol(symbol: ts.Symbol) {
|
|
const type = checker.getTypeOfSymbolAtLocation(
|
|
symbol,
|
|
symbol.valueDeclaration
|
|
);
|
|
const typeNode = checker.typeToTypeNode(type, undefined, buildFlags)!;
|
|
const typeName = symbol.getName();
|
|
const entry: DocEntry = {
|
|
name: typeName,
|
|
docs: ts.displayPartsToString(symbol.getDocumentationComment(checker)),
|
|
type: type.getSymbol()!.getName() as DocEntry["type"],
|
|
};
|
|
typeNode.forEachChild((ch) => {
|
|
if (ts.isTypeLiteralNode(ch)) {
|
|
const text = printer.printNode(
|
|
ts.EmitHint.Unspecified,
|
|
ch,
|
|
currentSourceFile
|
|
);
|
|
if (text !== "{}") {
|
|
entry.text = text;
|
|
}
|
|
/*
|
|
Go through each parameter.
|
|
ch.members.forEach(m => {
|
|
if (ts.isPropertySignature(m)) {
|
|
if (ts.isIdentifier(m.name)) {
|
|
data.parameters[m.name.text] = printer.printNode(
|
|
ts.EmitHint.Unspecified,
|
|
m.type!,
|
|
currentSourceFile
|
|
);
|
|
}
|
|
}
|
|
});
|
|
*/
|
|
}
|
|
});
|
|
data.push(entry);
|
|
}
|
|
|
|
/** True if this is visible outside this file, false otherwise */
|
|
function isNodeExported(node: ts.Node): boolean {
|
|
return (
|
|
// eslint-disable-next-line no-bitwise, @typescript-eslint/no-unnecessary-type-assertion
|
|
(ts.getCombinedModifierFlags(node as ts.Declaration) &
|
|
ts.ModifierFlags.Export) !==
|
|
0 ||
|
|
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
|
|
);
|
|
}
|
|
}
|
|
|
|
function prefixLines(text: string, prefix: string) {
|
|
return text.split("\n").join(`\n${prefix}`);
|
|
}
|
|
|
|
function getEventName(typeName: string) {
|
|
return (
|
|
typeName[0].toLocaleLowerCase() +
|
|
typeName.slice(1, typeName.length - "Event".length)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Removes "%future added value" from text. This is a placeholder type
|
|
* added by Relay to help with future proofness.
|
|
*/
|
|
function removeFutureAddedValue(text: string) {
|
|
return text
|
|
.replace(': "%future added value" | ', ": ")
|
|
.replace(' | "%future added value"', "");
|
|
}
|
|
|
|
/**
|
|
* Append or update previous documention in markdownFile.
|
|
*
|
|
* @param markdownFile The markdown file we want to inject the docs too.
|
|
* @param entries data as returned by gatherEntries.
|
|
*/
|
|
function emitDocs(markdownFile: string, entries: DocEntry[], verify = false) {
|
|
const previousContent = fs.existsSync(markdownFile)
|
|
? fs.readFileSync(markdownFile).toString()
|
|
: "";
|
|
const summary = stripIndent`
|
|
- ${entries
|
|
.map(
|
|
(e) => `<a href="#${getEventName(e.name)}">${getEventName(e.name)}</a>`
|
|
)
|
|
.join("\n - ")}
|
|
`;
|
|
const list = entries
|
|
.map(
|
|
(e) =>
|
|
codeBlock`
|
|
- ${
|
|
e.type === "ViewerEvent"
|
|
? `<a id="${getEventName(e.name)}">**${getEventName(e.name)}**</a>`
|
|
: `<a id="${getEventName(e.name)}">**${getEventName(
|
|
e.name
|
|
)}.success**, **${getEventName(e.name)}.error**</a>`
|
|
}: ${e.docs ? e.docs.replace("\n", " ") : ""}
|
|
${
|
|
e.text
|
|
? codeBlock`
|
|
\`\`\`ts
|
|
${removeFutureAddedValue(e.text)}
|
|
\`\`\`
|
|
`
|
|
: ""
|
|
}
|
|
`
|
|
)
|
|
.join("\n");
|
|
|
|
const output = stripIndent`
|
|
<!-- START docs:events -->
|
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN npm run docs:events -->
|
|
### Index
|
|
${prefixLines(summary, " ")}
|
|
|
|
### Events
|
|
${prefixLines(list, " ")}
|
|
<!-- END docs:events -->
|
|
`;
|
|
|
|
let newContent;
|
|
// Find previous block.
|
|
if (BLOCK_REGEXP.test(previousContent)) {
|
|
newContent = previousContent.replace(BLOCK_REGEXP, output);
|
|
} else {
|
|
newContent = previousContent + "\n" + output;
|
|
}
|
|
if (previousContent === newContent) {
|
|
// eslint-disable-next-line no-console
|
|
console.log(`${markdownFile} is up to date`);
|
|
return;
|
|
}
|
|
if (verify) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(
|
|
`${markdownFile} is outdated, please run \`npm run docs:events\``
|
|
);
|
|
process.exit(1);
|
|
return;
|
|
}
|
|
|
|
fs.writeFileSync(markdownFile, newContent);
|
|
// eslint-disable-next-line no-console
|
|
console.log(`Successfully injected documentation into ${markdownFile}`);
|
|
}
|
|
|
|
function main() {
|
|
if (process.argv.length < 4) {
|
|
throw new Error("Must provide path to events and a markdown file.");
|
|
}
|
|
|
|
const eventFile = process.argv[2];
|
|
const markdownFile = process.argv[3];
|
|
|
|
// Find tsconfig file.
|
|
const configFile = ts.findConfigFile(eventFile, fs.existsSync);
|
|
if (!configFile) {
|
|
throw new Error("tsconfig file not found");
|
|
}
|
|
const configText = fs.readFileSync(configFile).toString();
|
|
const result = ts.parseConfigFileTextToJson(configFile, configText);
|
|
if (result.error) {
|
|
throw result.error;
|
|
}
|
|
|
|
// Parse the JSON raw data into actual consumable compiler options.
|
|
const config = ts.parseJsonConfigFileContent(
|
|
result.config,
|
|
ts.sys,
|
|
path.dirname(configFile)
|
|
);
|
|
|
|
const entries = gatherEntries([eventFile], config.options);
|
|
emitDocs(markdownFile, entries, process.argv[4] === "--verify");
|
|
}
|
|
|
|
main();
|