diff --git a/gatsby-config.js b/gatsby-config.js index 6979a3f2..27d8eec5 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -19,6 +19,15 @@ module.exports = { path: `${__dirname}/src/images`, }, }, + { + resolve: "gatsby-source-lodash", + options: { + versions: [{ + version: '4.17.11', + url: 'https://raw.githubusercontent.com/lodash/lodash/4.17.11-npm/lodash.js', + }] + }, + }, `gatsby-plugin-react-svg`, `gatsby-transformer-sharp`, `gatsby-plugin-sharp`, diff --git a/plugins/gatsby-source-lodash/README.md b/plugins/gatsby-source-lodash/README.md new file mode 100644 index 00000000..f5c640e5 --- /dev/null +++ b/plugins/gatsby-source-lodash/README.md @@ -0,0 +1,25 @@ +# Gatsby Source Lodash + +A simple Gatsby Source Plugin to grab method metadata from Lodash. + +## Usage + +In the `gatsby-config.js` file, add `gatsby-source-lodash` to the `module.exports`. + +```js +module.exports = { + plugins: [ + ... + { + resolve: "gatsby-source-lodash", + options: { + versions: [{ + version: '4.17.11', + url: 'https://raw.githubusercontent.com/lodash/lodash/4.17.11-npm/lodash.js', + }] + }, + }, + ... + ] +} +``` diff --git a/plugins/gatsby-source-lodash/gatsby-node.js b/plugins/gatsby-source-lodash/gatsby-node.js new file mode 100644 index 00000000..eca7a5cf --- /dev/null +++ b/plugins/gatsby-source-lodash/gatsby-node.js @@ -0,0 +1,101 @@ +"use strict" + +const fetch = require("node-fetch") +const _ = require("lodash") +const Entry = require("./lib/entry.js") +const getEntries = Entry.getEntries + +exports.sourceNodes = ( + { actions, createNodeId, createContentDigest }, + configOptions +) => { + const { createNode } = actions + + // Gatsby adds a configOption that's not needed for this plugin, delete it + delete configOptions.plugins + + // TODO: Extend this to include multiple versions + const url = configOptions.versions[0].url + const version = configOptions.versions[0].version + + const processEntry = entry => { + try { + // Exit early if the entry is private or has no name + if (!entry || !entry.getName() || entry.isPrivate()) { + return + } + } catch (err) { + // Some of the non-lodash methods were throwing a hard to trace error + // from the lib code. Rather than trace back, it was easier to catch + // since they aren't part of lodash. + return + } + + // Special handling of aliases to get call names. Without this, there are + // circular JS object references. + const aliases = entry.getAliases().map(alias => { + const member = entry.getMembers(0) || "" + const name = alias.getName() + return `${member}.${name}` + }) + + const params = entry + .getParams() + .map(([type, name, desc]) => ({ type, name, desc })) + + const entryData = { + aliases, + call: entry.getCall(), + category: entry.getCategory(), + desc: entry.getDesc(), + example: entry.getExample(), + hash: entry.getHash(), + isAlias: entry.isAlias(), + isCtor: entry.isCtor(), + isFunction: entry.isFunction(), + isLicense: entry.isLicense(), + isPlugin: entry.isPlugin(), + isStatic: entry.isStatic(), + lineNumber: entry.getLineNumber(), + members: entry.getMembers(), + name: entry.getName(), + params, + related: entry.getRelated(), + returns: entry.getReturns(), + since: entry.getSince(), + type: entry.getType(), + version, + } + + const nodeData = { + ...entryData, + children: [], + id: createNodeId( + `lodash_method_${version}_${entryData.hash}_${entryData.lineNumber}` + ), + internal: { + content: JSON.stringify(entryData), + contentDigest: createContentDigest(JSON.stringify(entryData)), + type: "LodashMethod", + }, + parent: null, + } + + return nodeData + } + + return fetch(url) + .then(res => res.text()) + .then(body => { + const entries = getEntries(body) + + // For each entry, create node. + _.each(entries, entry => { + entry = new Entry(entry, body) + const nodeData = processEntry(entry) + if (nodeData) { + createNode(nodeData) + } + }) + }) +} diff --git a/plugins/gatsby-source-lodash/lib/alias.js b/plugins/gatsby-source-lodash/lib/alias.js new file mode 100644 index 00000000..df3c7e72 --- /dev/null +++ b/plugins/gatsby-source-lodash/lib/alias.js @@ -0,0 +1,259 @@ +"use strict" + +const _ = require("lodash") + +/*----------------------------------------------------------------------------*/ + +/** + * The Alias constructor. + * + * @constructor + * @param {string} name The alias name. + * @param {Object} owner The alias owner. + */ +function Alias(name, owner) { + this._owner = owner + this._name = name +} + +/** + * Extracts the entry's `alias` objects. + * + * @memberOf Alias + * @param {number} [index] The index of the array value to return. + * @returns {Array|string} Returns the entry's `alias` objects. + */ +function getAliases(index) { + return index == null ? [] : undefined +} + +/** + * Extracts the function call from the owner entry. + * + * @memberOf Alias + * @returns {string} Returns the function call. + */ +function getCall() { + return this._owner.getCall() +} + +/** + * Extracts the owner entry's `category` data. + * + * @memberOf Alias + * @returns {string} Returns the owner entry's `category` data. + */ +function getCategory() { + return this._owner.getCategory() +} + +/** + * Extracts the owner entry's description. + * + * @memberOf Alias + * @returns {string} Returns the owner entry's description. + */ +function getDesc() { + return this._owner.getDesc() +} + +/** + * Extracts the owner entry's `example` data. + * + * @memberOf Alias + * @returns {string} Returns the owner entry's `example` data. + */ +function getExample() { + return this._owner.getExample() +} + +/** + * Extracts the entry's hash value for permalinking. + * + * @memberOf Alias + * @param {string} [style] The hash style. + * @returns {string} Returns the entry's hash value (without a hash itself). + */ +function getHash(style) { + return this._owner.getHash(style) +} + +/** + * Resolves the owner entry's line number. + * + * @memberOf Alias + * @returns {number} Returns the owner entry's line number. + */ +function getLineNumber() { + return this._owner.getLineNumber() +} + +/** + * Extracts the owner entry's `member` data. + * + * @memberOf Alias + * @param {number} [index] The index of the array value to return. + * @returns {Array|string} Returns the owner entry's `member` data. + */ +function getMembers(index) { + return this._owner.getMembers(index) +} + +/** + * Extracts the owner entry's `name` data. + * + * @memberOf Alias + * @returns {string} Returns the owner entry's `name` data. + */ +function getName() { + return this._name +} + +/** + * Gets the owner entry object. + * + * @memberOf Alias + * @returns {Object} Returns the owner entry. + */ +function getOwner() { + return this._owner +} + +/** + * Extracts the owner entry's `param` data. + * + * @memberOf Alias + * @param {number} [index] The index of the array value to return. + * @returns {Array} Returns the owner entry's `param` data. + */ +function getParams(index) { + return this._owner.getParams(index) +} + +/** + * Extracts the owner entry's `returns` data. + * + * @memberOf Alias + * @returns {string} Returns the owner entry's `returns` data. + */ +function getReturns() { + return this._owner.getReturns() +} + +/** + * Extracts the owner entry's `since` data. + * + * @memberOf Alias + * @returns {string} Returns the owner entry's `since` data. + */ +function getSince() { + return this._owner.getSince() +} + +/** + * Extracts the owner entry's `type` data. + * + * @memberOf Alias + * @returns {string} Returns the owner entry's `type` data. + */ +function getType() { + return this._owner.getType() +} + +/** + * Checks if the entry is an alias. + * + * @memberOf Alias + * @returns {boolean} Returns `true`. + */ +function isAlias() { + return true +} + +/** + * Checks if the owner entry is a constructor. + * + * @memberOf Alias + * @returns {boolean} Returns `true` if a constructor, else `false`. + */ +function isCtor() { + return this._owner.isCtor() +} + +/** + * Checks if the entry is a function reference. + * + * @memberOf Alias + * @returns {boolean} Returns `true` if the entry is a function reference, else `false`. + */ +function isFunction() { + return this._owner.isFunction() +} + +/** + * Checks if the owner entry is a license. + * + * @memberOf Alias + * @returns {boolean} Returns `true` if a license, else `false`. + */ +function isLicense() { + return this._owner.isLicense() +} + +/** + * Checks if the owner entry *is* assigned to a prototype. + * + * @memberOf Alias + * @returns {boolean} Returns `true` if assigned to a prototype, else `false`. + */ +function isPlugin() { + return this._owner.isPlugin() +} + +/** + * Checks if the owner entry is private. + * + * @memberOf Alias + * @returns {boolean} Returns `true` if private, else `false`. + */ +function isPrivate() { + return this._owner.isPrivate() +} + +/** + * Checks if the owner entry is *not* assigned to a prototype. + * + * @memberOf Alias + * @returns {boolean} Returns `true` if not assigned to a prototype, else `false`. + */ +function isStatic() { + return this._owner.isStatic() +} + +/*----------------------------------------------------------------------------*/ + +_.assign(Alias.prototype, { + getAliases, + getCall, + getCategory, + getDesc, + getExample, + getHash, + getLineNumber, + getMembers, + getName, + getOwner, + getParams, + getReturns, + getSince, + getType, + isAlias, + isCtor, + isFunction, + isLicense, + isPlugin, + isPrivate, + isStatic, +}) + +module.exports = Alias diff --git a/plugins/gatsby-source-lodash/lib/entry.js b/plugins/gatsby-source-lodash/lib/entry.js new file mode 100644 index 00000000..93201661 --- /dev/null +++ b/plugins/gatsby-source-lodash/lib/entry.js @@ -0,0 +1,581 @@ +"use strict" + +const _ = require("lodash") +const fp = require("lodash/fp") +const os = require("os") +const Alias = require("./alias.js") +const util = require("./util.js") + +/*----------------------------------------------------------------------------*/ + +/** + * Gets the param type of `tag`. + * + * @private + * @param {Object} tag The param tag to inspect. + * @returns {string} Returns the param type. + */ +function getParamType(tag) { + let expression = tag.expression + let result = "" + const type = tag.type + + switch (type) { + case "AllLiteral": + result = "*" + break + + case "NameExpression": + result = _.toString(tag.name) + break + + case "RestType": + result = "..." + result + break + + case "TypeApplication": + expression = undefined + result = _(tag) + .chain() + .get("applications") + .map( + _.flow( + getParamType, + fp.add(fp, "[]") + ) + ) + .sort(util.compareNatural) + .join("|") + .value() + break + + case "UnionType": + result = _(tag) + .chain() + .get("elements") + .map(getParamType) + .sort(util.compareNatural) + .join("|") + .value() + } + if (expression) { + result += getParamType(expression) + } + return type === "UnionType" ? "(" + result + ")" : result +} + +/** + * Gets an `entry` tag by `tagName`. + * + * @private + * @param {Object} entry The entry to inspect. + * @param {string} tagName The name of the tag. + * @returns {null|Object} Returns the tag. + */ +function getTag(entry, tagName) { + const parsed = entry.parsed + return _.find(parsed.tags, ["title", tagName]) || null +} + +/** + * Gets an `entry` tag value by `tagName`. + * + * @private + * @param {Object} entry The entry to inspect. + * @param {string} tagName The name of the tag. + * @returns {string} Returns the tag value. + */ +function getValue(entry, tagName) { + const parsed = entry.parsed + let result = parsed.description + const tag = getTag(entry, tagName) + + if (tagName === "alias") { + result = _.get(tag, "name") + + // Doctrine can't parse alias tags containing multiple values so extract + // them from the error message. + const error = _.first(_.get(tag, "errors")) + if (error) { + result += error.replace(/^[^']*'|'[^']*$/g, "") + } + } else if (tagName === "type") { + result = _.get(tag, "type.name") + } else if (tagName !== "description") { + result = _.get(tag, "name") || _.get(tag, "description") + } + return tagName === "example" ? _.toString(result) : util.format(result) +} + +/** + * Checks if `entry` has a tag of `tagName`. + * + * @private + * @param {Object} entry The entry to inspect. + * @param {string} tagName The name of the tag. + * @returns {boolean} Returns `true` if the tag is found, else `false`. + */ +function hasTag(entry, tagName) { + return getTag(entry, tagName) !== null +} + +/** + * Converts CR+LF line endings to LF. + * + * @private + * @param {string} str The string to convert. + * @returns {string} Returns the converted string. + */ +function normalizeEOL(str) { + return str.replace(/\r\n/g, "\n") +} + +/*----------------------------------------------------------------------------*/ + +/** + * The Entry constructor. + * + * @constructor + * @param {string} entry The documentation entry to analyse. + * @param {string} source The source code. + * @param {string} [lang='js'] The language highlighter used for code examples. + */ +function Entry(entry, source, lang) { + const normalizedEntry = normalizeEOL(entry) + + this.entry = normalizedEntry + this.lang = lang == null ? "js" : lang + this.parsed = util.parse(normalizedEntry.replace(/(\*)\/\s*.+$/, "*")) + this.source = normalizeEOL(source) + this.getCall = _.memoize(this.getCall) + this.getCategory = _.memoize(this.getCategory) + this.getDesc = _.memoize(this.getDesc) + this.getExample = _.memoize(this.getExample) + this.getHash = _.memoize(this.getHash) + this.getLineNumber = _.memoize(this.getLineNumber) + this.getName = _.memoize(this.getName) + this.getRelated = _.memoize(this.getRelated) + this.getReturns = _.memoize(this.getReturns) + this.getSince = _.memoize(this.getSince) + this.getType = _.memoize(this.getType) + this.isAlias = _.memoize(this.isAlias) + this.isCtor = _.memoize(this.isCtor) + this.isFunction = _.memoize(this.isFunction) + this.isLicense = _.memoize(this.isLicense) + this.isPlugin = _.memoize(this.isPlugin) + this.isPrivate = _.memoize(this.isPrivate) + this.isStatic = _.memoize(this.isStatic) + this._aliases = this._members = this._params = undefined +} + +/** + * Extracts the documentation entries from source code. + * + * @static + * @memberOf Entry + * @param {string} source The source code. + * @returns {Array} Returns the array of entries. + */ +function getEntries(source) { + return _.toString(source).match(/\/\*\*(?![-!])[\s\S]*?\*\/\s*.+/g) || [] +} + +/** + * Extracts the entry's `alias` objects. + * + * @memberOf Entry + * @param {number} index The index of the array value to return. + * @returns {Array|string} Returns the entry's `alias` objects. + */ +function getAliases(index) { + if (this._aliases === undefined) { + const owner = this + this._aliases = _(getValue(this, "alias")) + .split(/,\s*/) + .compact() + .sort(util.compareNatural) + .map(function(value) { + return new Alias(value, owner) + }) + .value() + } + const result = this._aliases + return index === undefined ? result : result[index] +} + +/** + * Extracts the function call from the entry. + * + * @memberOf Entry + * @returns {string} Returns the function call. + */ +function getCall() { + let result = _.trim( + _.get(/\*\/\s*(?:function\s+)?([^\s(]+)\s*\(/.exec(this.entry), 1) + ) + if (!result) { + result = _.trim(_.get(/\*\/\s*(.*?)[:=,]/.exec(this.entry), 1)) + result = /['"]$/.test(result) + ? _.trim(result, "\"'") + : result + .split(".") + .pop() + .split(/^(?:const|let|var) /) + .pop() + } + const name = getValue(this, "name") || result + if (!this.isFunction()) { + return name + } + const params = this.getParams() + result = _.castArray(result) + + // Compile the function call syntax. + _.each(params, function(param) { + const paramValue = param[1] + const parentParam = _.get(/\w+(?=\.[\w.]+)/.exec(paramValue), 0) + + const parentIndex = + parentParam === null + ? -1 + : _.findIndex(params, function(parameter) { + return _.trim(parameter[1], "[]").split(/\s*=/)[0] === parentParam + }) + + // Skip params that are properties of other params (e.g. `options.leading`). + if (_.get(params[parentIndex], 0) !== "Object") { + result.push(paramValue) + } + }) + + // Format the function call. + return name + "(" + result.slice(1).join(", ") + ")" +} + +/** + * Extracts the entry's `category` data. + * + * @memberOf Entry + * @returns {string} Returns the entry's `category` data. + */ +function getCategory() { + const result = getValue(this, "category") + return result || (this.getType() === "Function" ? "Methods" : "Properties") +} + +/** + * Extracts the entry's description. + * + * @memberOf Entry + * @returns {string} Returns the entry's description. + */ +function getDesc() { + const type = this.getType() + const result = getValue(this, "description") + + return !result || type === "Function" || type === "unknown" + ? result + : "(" + _.trim(type.replace(/\|/g, ", "), "()") + "): " + result +} + +/** + * Extracts the entry's `example` data. + * + * @memberOf Entry + * @returns {string} Returns the entry's `example` data. + */ +function getExample() { + const result = getValue(this, "example") + return result && "```" + this.lang + "\n" + result + "\n```" +} + +/** + * Extracts the entry's hash value for permalinking. + * + * @memberOf Entry + * @param {string} [style] The hash style. + * @returns {string} Returns the entry's hash value (without a hash itself). + */ +function getHash(style) { + let result = _.toString(this.getMembers(0)) + if (style === "github") { + if (result) { + result += this.isPlugin() ? "prototype" : "" + } + result += this.getCall() + return result + .replace(/[\\.=|'"(){}\[\]\t ]/g, "") + .replace(/[#,]+/g, "-") + .toLowerCase() + } + if (result) { + result += "-" + (this.isPlugin() ? "prototype-" : "") + } + result += this.isAlias() ? this.getOwner().getName() : this.getName() + return result.replace(/\./g, "-").replace(/^_-/, "") +} + +/** + * Resolves the entry's line number. + * + * @memberOf Entry + * @returns {number} Returns the entry's line number. + */ +function getLineNumber() { + const lines = this.source + .slice(0, this.source.indexOf(this.entry) + this.entry.length) + .match(/\n/g) + .slice(1) + + // Offset by 2 because the first line number is before a line break and the + // last line doesn't include a line break. + return lines.length + 2 +} + +/** + * Extracts the entry's `member` data. + * + * @memberOf Entry + * @param {number} [index] The index of the array value to return. + * @returns {Array|string} Returns the entry's `member` data. + */ +function getMembers(index) { + if (this._members === undefined) { + this._members = _(getValue(this, "member") || getValue(this, "memberOf")) + .split(/,\s*/) + .compact() + .sort(util.compareNatural) + .value() + } + const result = this._members + return index === undefined ? result : result[index] +} + +/** + * Extracts the entry's `name` data. + * + * @memberOf Entry + * @returns {string} Returns the entry's `name` data. + */ +function getName() { + return hasTag(this, "name") + ? getValue(this, "name") + : _.toString(_.first(this.getCall().split("("))) +} + +/** + * Extracts the entry's `param` data. + * + * @memberOf Entry + * @param {number} [index] The index of the array value to return. + * @returns {Array} Returns the entry's `param` data. + */ +function getParams(index) { + if (this._params === undefined) { + this._params = _(this.parsed.tags) + .filter(["title", "param"]) + .filter("name") + .map(function(tag) { + const defaultValue = tag["default"] + const desc = util.format(tag.description) + let name = _.toString(tag.name) + const type = getParamType(tag.type) + + if (defaultValue != null) { + name += "=" + defaultValue + } + if (_.get(tag, "type.type") === "OptionalType") { + name = "[" + name + "]" + } + return [type, name, desc] + }) + .value() + } + const result = this._params + return index === undefined ? result : result[index] +} + +/** + * Extracts the entry's `see` data. + * + * @memberOf Entry + * @returns {array} Returns the entry's `see` data as links. + */ +function getRelated() { + const relatedValues = getValue(this, "see") + if (relatedValues && relatedValues.trim().length > 0) { + const relatedItems = relatedValues + .split(",") + .map(relatedItem => relatedItem.trim()) + return relatedItems.map( + relatedItem => "[" + relatedItem + "](#" + relatedItem + ")" + ) + } else { + return [] + } +} + +/** + * Extracts the entry's `returns` data. + * + * @memberOf Entry + * @returns {array} Returns the entry's `returns` data. + */ +function getReturns() { + const tag = getTag(this, "returns") + const desc = _.toString(_.get(tag, "description")) + const type = _.toString(_.get(tag, "type.name")) || "*" + + return tag ? [type, desc] : [] +} + +/** + * Extracts the entry's `since` data. + * + * @memberOf Entry + * @returns {string} Returns the entry's `since` data. + */ +function getSince() { + return getValue(this, "since") +} + +/** + * Extracts the entry's `type` data. + * + * @memberOf Entry + * @returns {string} Returns the entry's `type` data. + */ +function getType() { + const result = getValue(this, "type") + if (!result) { + return this.isFunction() ? "Function" : "unknown" + } + return /^(?:array|function|object|regexp)$/.test(result) + ? _.capitalize(result) + : result +} + +/** + * Checks if the entry is an alias. + * + * @memberOf Entry + * @type {Function} + * @returns {boolean} Returns `false`. + */ +const isAlias = _.constant(false) + +/** + * Checks if the entry is a constructor. + * + * @memberOf Entry + * @returns {boolean} Returns `true` if a constructor, else `false`. + */ +function isCtor() { + return hasTag(this, "constructor") +} + +/** + * Checks if the entry is a function reference. + * + * @memberOf Entry + * @returns {boolean} Returns `true` if the entry is a function reference, else `false`. + */ +function isFunction() { + return !!( + this.isCtor() || + _.size(this.getParams()) || + _.size(this.getReturns()) || + hasTag(this, "function") || + /\*\/\s*(?:function\s+)?[^\s(]+\s*\(/.test(this.entry) + ) +} + +/** + * Checks if the entry is a license. + * + * @memberOf Entry + * @returns {boolean} Returns `true` if a license, else `false`. + */ +function isLicense() { + return hasTag(this, "license") +} + +/** + * Checks if the entry *is* assigned to a prototype. + * + * @memberOf Entry + * @returns {boolean} Returns `true` if assigned to a prototype, else `false`. + */ +function isPlugin() { + return !this.isCtor() && !this.isPrivate() && !this.isStatic() +} + +/** + * Checks if the entry is private. + * + * @memberOf Entry + * @returns {boolean} Returns `true` if private, else `false`. + */ +function isPrivate() { + return ( + this.isLicense() || hasTag(this, "private") || _.isEmpty(this.parsed.tags) + ) +} + +/** + * Checks if the entry is *not* assigned to a prototype. + * + * @memberOf Entry + * @returns {boolean} Returns `true` if not assigned to a prototype, else `false`. + */ +function isStatic() { + const isPublic = !this.isPrivate() + let result = isPublic && hasTag(this, "static") + + // Get the result in cases where it isn't explicitly stated. + if (isPublic && !result) { + const parent = _.last(_.toString(this.getMembers(0)).split(/[#.]/)) + if (!parent) { + return true + } + const source = this.source + _.each(getEntries(source), function(entry) { + entry = new Entry(entry, source) + if (entry.getName() === parent) { + result = !entry.isCtor() + return false + } + }) + } + return result +} + +/*----------------------------------------------------------------------------*/ + +Entry.getEntries = getEntries + +_.assign(Entry.prototype, { + getAliases, + getCall, + getCategory, + getDesc, + getExample, + getHash, + getLineNumber, + getMembers, + getName, + getParams, + getRelated, + getReturns, + getSince, + getType, + isAlias, + isCtor, + isFunction, + isLicense, + isPlugin, + isPrivate, + isStatic, +}) + +module.exports = Entry diff --git a/plugins/gatsby-source-lodash/lib/util.js b/plugins/gatsby-source-lodash/lib/util.js new file mode 100644 index 00000000..d731ee35 --- /dev/null +++ b/plugins/gatsby-source-lodash/lib/util.js @@ -0,0 +1,98 @@ +"use strict" + +const _ = require("lodash") +const doctrine = require("doctrine") + +const reCode = /`.*?`/g +const reToken = /@@token@@/g +const split = String.prototype.split +const token = "@@token@@" + +/*----------------------------------------------------------------------------*/ + +/** + * The `Array#sort` comparator to produce a + * [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order). + * + * @memberOf util + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {number} Returns the sort order indicator for `value`. + */ +function compareNatural(value, other) { + let index = -1 + const valParts = split.call(value, ".") + const valLength = valParts.length + const othParts = split.call(other, ".") + const othLength = othParts.length + const length = Math.min(valLength, othLength) + + while (++index < length) { + const valPart = valParts[index] + const othPart = othParts[index] + + if (valPart > othPart && othPart !== "prototype") { + return 1 + } else if (valPart < othPart && valPart !== "prototype") { + return -1 + } + } + return valLength > othLength ? 1 : valLength < othLength ? -1 : 0 +} + +/** + * Performs common string formatting operations. + * + * @memberOf util + * @param {string} str The string to format. + * @returns {string} Returns the formatted string. + */ +function format(str) { + let copy = _.toString(str) + + // Replace all code snippets with a token. + let snippets = [] + copy = copy.replace(reCode, function(match) { + snippets.push(match) + return token + }) + + return ( + copy + // Add line breaks. + .replace(/:\n(?=[\t ]*\S)/g, ":
\n") + .replace(/\n( *)[-*](?=[\t ]+\S)/g, "\n
\n$1*") + .replace(/^[\t ]*\n/gm, "
\n
\n") + // Normalize whitespace. + .replace(/\n +/g, " ") + // Italicize parentheses. + .replace(/(^|\s)(\(.+\))/g, "$1*$2*") + // Mark numbers as inline code. + .replace(/[\t ](-?\d+(?:.\d+)?)(?!\.[^\n])/g, " `$1`") + // Replace all tokens with code snippets. + .replace(reToken, function(match) { + return snippets.shift() + }) + .trim() + ) +} + +/** + * Parses the JSDoc `comment` into an object. + * + * @memberOf util + * @param {string} comment The comment to parse. + * @returns {Object} Returns the parsed object. + */ +const parse = _.partial(doctrine.parse, _, { + lineNumbers: true, + recoverable: true, + sloppy: true, + unwrap: true, +}) + +module.exports = { + compareNatural, + format, + parse, +} diff --git a/plugins/gatsby-source-lodash/package-lock.json b/plugins/gatsby-source-lodash/package-lock.json new file mode 100644 index 00000000..08f8f3e1 --- /dev/null +++ b/plugins/gatsby-source-lodash/package-lock.json @@ -0,0 +1,31 @@ +{ + "name": "gatsby-source-lodash", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "node-fetch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", + "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" + } + } +} diff --git a/plugins/gatsby-source-lodash/package.json b/plugins/gatsby-source-lodash/package.json new file mode 100644 index 00000000..5152ab02 --- /dev/null +++ b/plugins/gatsby-source-lodash/package.json @@ -0,0 +1,20 @@ +{ + "name": "gatsby-source-lodash", + "version": "0.1.0", + "description": "Gatsby Source Plugin to grab method metadata from Lodash.", + "main": "gatsby-node.js", + "scripts": {}, + "keywords": [], + "author": "Zack Hall ", + "contributors": [ + "John-David Dalton ", + "Mathias Bynens ", + "Zack Hall " + ], + "license": "MIT", + "dependencies": { + "doctrine": "^3.0.0", + "lodash": "^4.17.11", + "node-fetch": "^2.3.0" + } +}