summaryrefslogtreecommitdiff
path: root/date-fns/scripts/build/docs.js
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-08-23 16:46:06 -0300
committerSebastian <sebasjm@gmail.com>2021-08-23 16:48:30 -0300
commit38acabfa6089ab8ac469c12b5f55022fb96935e5 (patch)
tree453dbf70000cc5e338b06201af1eaca8343f8f73 /date-fns/scripts/build/docs.js
parentf26125e039143b92dc0d84e7775f508ab0cdcaa8 (diff)
downloadnode-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.tar.gz
node-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.tar.bz2
node-vendor-38acabfa6089ab8ac469c12b5f55022fb96935e5.zip
added web vendorsHEADmaster
Diffstat (limited to 'date-fns/scripts/build/docs.js')
-rwxr-xr-xdate-fns/scripts/build/docs.js380
1 files changed, 380 insertions, 0 deletions
diff --git a/date-fns/scripts/build/docs.js b/date-fns/scripts/build/docs.js
new file mode 100755
index 0000000..85c99eb
--- /dev/null
+++ b/date-fns/scripts/build/docs.js
@@ -0,0 +1,380 @@
+#!/usr/bin/env node
+
+/**
+ * @file
+ * The script generates docs.json used as the source of truth
+ * for the source code generators (FP, typings, etc.).
+ *
+ * It's a part of the build process.
+ */
+
+const os = require('os')
+const pLimit = require('p-limit')
+const fs = require('fs/promises')
+const path = require('path')
+const cloneDeep = require('lodash.clonedeep')
+const jsDocParser = require('jsdoc-to-markdown')
+const listFns = require('../_lib/listFns')
+const docsConfig = require('../../docs/index.js')
+
+const docsPath = path.resolve(process.cwd(), 'tmp/docs.json')
+
+generateDocsFromSource()
+ .then(generatedDocsObj)
+ .then(injectStaticDocsToDocsObj)
+ .then(injectSharedDocsToDocsObj)
+ .then(writeDocsFile)
+ .catch(reportErrors)
+
+/**
+ * Generates docs object from a list of functions using extended JSDoc format.
+ */
+async function generateDocsFromSource() {
+ const fns = await listFns()
+
+ const limit = pLimit(os.cpus().length)
+
+ const configFile = path.resolve(process.cwd(), 'jsdoc2md.json')
+
+ const jobs = fns.map((fn) => {
+ return limit(() =>
+ jsDocParser
+ .getTemplateData({
+ files: fn.fullPath,
+ 'no-cache': true,
+ configure: configFile,
+ })
+ .then((result) => result[0])
+ )
+ })
+
+ const docsResult = await Promise.all(jobs)
+
+ return docsResult
+ .map((doc) => {
+ const pureTag =
+ doc.customTags && doc.customTags.find((t) => t.tag === 'pure')
+ const pure = (pureTag && pureTag.value) !== 'false'
+ return {
+ type: 'jsdoc',
+ kind: 'function',
+ urlId: doc.name,
+ category: doc.category,
+ title: doc.name,
+ description: doc.summary,
+ content: doc,
+ pure,
+ }
+ })
+ .reduce(
+ (array, doc) =>
+ array
+ .concat(generateFnDoc(doc))
+ .concat(
+ doc.pure
+ ? [generateFPFnDoc(doc)].concat(
+ generateFPFnWithOptionsDoc(doc) || []
+ )
+ : []
+ ),
+ []
+ )
+}
+
+/**
+ * Generates docs object.
+ */
+function generatedDocsObj(docs) {
+ return groupDocs(docs, docsConfig.groups)
+}
+
+/**
+ * Injects static docs (markdown documents specified in the config file)
+ * to docs object.
+ */
+function injectStaticDocsToDocsObj(docsFileObj) {
+ return getListOfStaticDocs()
+ .then((staticDocs) => {
+ staticDocs.forEach((staticDoc) => {
+ docsFileObj[staticDoc.category].push(staticDoc)
+ })
+ return docsFileObj
+ })
+ .catch(reportErrors)
+}
+
+/**
+ * Injects shared docs to docs object.
+ */
+function injectSharedDocsToDocsObj(docsFileObj) {
+ return generateSharedDocs()
+ .then((sharedDocs) => {
+ sharedDocs.forEach((sharedDoc) => {
+ docsFileObj[sharedDoc.category].push(sharedDoc)
+ })
+ return docsFileObj
+ })
+ .catch(reportErrors)
+}
+
+/**
+ * Prints an error and exits the process with 1 status code.
+ */
+function reportErrors(err) {
+ console.error(err.stack)
+ process.exit(1)
+}
+
+/**
+ * Writes docs file.
+ */
+function writeDocsFile(docsFileObj) {
+ return fs.writeFile(docsPath, JSON.stringify(docsFileObj))
+}
+
+/**
+ * Groups passed docs list.
+ */
+function groupDocs(docs, groups) {
+ return docs.reduce((acc, doc) => {
+ ;(acc[doc.category] = acc[doc.category] || []).push(doc)
+ return acc
+ }, buildGroupsTemplate(groups))
+}
+
+/**
+ * Builds an object where the key is a group name and the value is
+ * an empty array. Pre-generated docs object allows to preserve the desired
+ * groups order.
+ */
+function buildGroupsTemplate(groups) {
+ return groups.reduce((acc, group) => {
+ acc[group] = []
+ return acc
+ }, {})
+}
+
+/**
+ * Returns promise to list of static docs with its contents.
+ */
+function getListOfStaticDocs() {
+ return Promise.all(
+ docsConfig.staticDocs.map((staticDoc) => {
+ return fs
+ .readFile(staticDoc.path)
+ .then((docContent) => docContent.toString())
+ .then((content) => Object.assign({ content }, staticDoc))
+ .catch(reportErrors)
+ })
+ )
+}
+
+/**
+ * Returns promise to list of shared docs with its contents.
+ */
+function generateSharedDocs() {
+ const docs = docsConfig.sharedDocs
+ .map(
+ (fn) =>
+ jsDocParser.getTemplateDataSync({
+ files: fn.fullPath,
+ 'no-cache': true,
+ })[0]
+ )
+ .map((doc) => ({
+ type: 'jsdoc',
+ kind: 'typedef',
+ urlId: doc.name,
+ category: doc.category,
+ title: doc.name,
+ description: doc.summary,
+ content: doc,
+ properties: paramsToTree(doc.properties),
+ }))
+
+ return Promise.resolve(docs)
+}
+
+function generateFnDoc(dirtyDoc) {
+ const doc = cloneDeep(dirtyDoc)
+
+ const isFPFn = false
+ const { urlId, title } = doc
+ const args = paramsToTree(doc.content.params)
+
+ return Object.assign(doc, {
+ isFPFn,
+ args,
+ relatedDocs: Object.assign(
+ { default: urlId, fp: `fp/${urlId}` },
+ withOptions(args) ? { fpWithOptions: `fp/${urlId}WithOptions` } : {}
+ ),
+ usage: generateUsage(title, isFPFn),
+ usageTabs: generateUsageTabs(isFPFn),
+ syntax: generateSyntaxString(title, args, isFPFn),
+ })
+}
+
+function generateFPFnDoc(dirtyDoc) {
+ const doc = cloneDeep(dirtyDoc)
+
+ const isFPFn = true
+ const { urlId, title } = doc
+ const exceptions = doc.content.exceptions.filter(
+ (exception) => !exception.description.includes('options.')
+ )
+ const params = doc.content.params
+ .filter((param) => !param.name.startsWith('options'))
+ .reverse()
+ const args = paramsToTree(params)
+
+ return Object.assign(doc, {
+ isFPFn,
+ args,
+ generatedFrom: title,
+ urlId: `fp/${urlId}`,
+ relatedDocs: Object.assign(
+ { default: urlId, fp: `fp/${urlId}` },
+ withOptions(args) ? { fpWithOptions: `fp/${urlId}WithOptions` } : {}
+ ),
+ usage: generateUsage(title, isFPFn),
+ usageTabs: generateUsageTabs(isFPFn),
+ syntax: generateSyntaxString(title, args, isFPFn),
+
+ content: Object.assign(doc.content, {
+ exceptions,
+ params,
+ examples:
+ 'See [FP Guide](https://date-fns.org/docs/FP-Guide) for more information',
+ }),
+ })
+}
+
+function generateFPFnWithOptionsDoc(dirtyDoc) {
+ const doc = cloneDeep(dirtyDoc)
+
+ const isFPFn = true
+ const { urlId, title } = doc
+ const params = doc.content.params
+ .map((param) => {
+ if (!param.name.includes('.')) {
+ param.optional = false
+ }
+ return param
+ })
+ .reverse()
+ const args = paramsToTree(params)
+
+ if (!withOptions(args)) return
+
+ return Object.assign(doc, {
+ isFPFn,
+ args,
+ generatedFrom: title,
+ title: `${title}WithOptions`,
+ urlId: `fp/${urlId}WithOptions`,
+ relatedDocs: {
+ default: urlId,
+ fp: `fp/${urlId}`,
+ fpWithOptions: `fp/${urlId}WithOptions`,
+ },
+ usage: generateUsage(title, isFPFn),
+ usageTabs: generateUsageTabs(isFPFn),
+ syntax: generateSyntaxString(title, args, isFPFn),
+
+ content: Object.assign(doc.content, {
+ params,
+ id: `${doc.content.id}WithOptions`,
+ longname: `${doc.content.longname}WithOptions`,
+ name: `${doc.content.name}WithOptions`,
+ examples:
+ 'See [FP Guide](https://date-fns.org/docs/FP-Guide) for more information',
+ }),
+ })
+}
+
+function withOptions(args) {
+ return args && args[0].name === 'options'
+}
+
+function generateUsageTabs(isFPFn) {
+ return isFPFn
+ ? ['commonjs', 'es2015', 'esm']
+ : ['commonjs', 'umd', 'es2015', 'esm']
+}
+
+function generateUsage(name, isFPFn) {
+ const submodule = isFPFn ? '/fp' : ''
+
+ let usage = {
+ commonjs: {
+ title: 'CommonJS',
+ code: `var ${name} = require('date-fns${submodule}/${name}')`,
+ },
+
+ es2015: {
+ title: 'ES 2015',
+ code: `import ${name} from 'date-fns${submodule}/${name}'`,
+ },
+
+ esm: {
+ title: 'ESM',
+ code: `import { ${name} } from 'date-fns${
+ submodule && `/esm/${submodule}`
+ }'`,
+ text:
+ 'See [ECMAScript Modules guide](https://date-fns.org/docs/ECMAScript-Modules) for more information',
+ },
+ }
+
+ return usage
+}
+
+function paramsToTree(dirtyParams) {
+ if (!dirtyParams) {
+ return null
+ }
+
+ const params = cloneDeep(dirtyParams)
+
+ const paramIndices = params.reduce((result, { name }, index) => {
+ result[name] = index
+ return result
+ }, {})
+
+ return params
+ .map((param) => {
+ const { name, isProperty } = param
+
+ const indexOfDot = name.indexOf('.')
+
+ if (indexOfDot >= 0 && !isProperty) {
+ const parentIndex = paramIndices[name.substring(0, indexOfDot)]
+ const parent = params[parentIndex]
+
+ param.name = name.substring(indexOfDot + 1)
+ param.isProperty = true
+ if (!parent.props) {
+ parent.props = [param]
+ } else {
+ parent.props.push(param)
+ }
+ }
+
+ return param
+ })
+ .filter((param) => !param.isProperty)
+}
+
+function generateSyntaxString(name, args, isFPFn) {
+ if (!args) {
+ return undefined
+ } else if (isFPFn) {
+ return args.reduce((acc, arg) => acc.concat(`(${arg.name})`), name)
+ } else {
+ const argsString = args
+ .map((arg) => (arg.optional ? `[${arg.name}]` : arg.name))
+ .join(', ')
+ return `${name}(${argsString})`
+ }
+}