refactor(jsdoc): eliminate global tag dictionary

And remove final dependency on `jsdoc/env` outside `jsdoc.js`.

In some cases, `jsdoc/util/templateHelper` functions now take a `dependencies` argument even when it looks like they could get the dependencies from a `doclet` parameter. That's because non-enumerable properties don't survive when the doclets are added to TaffyDB; as a result, the dependencies are no longer attached to each doclet by the time the template runs. This limitation should go away when we stop using TaffyDB.
This commit is contained in:
Jeff Williams 2021-10-23 11:59:57 -07:00
parent 004ce7392c
commit 44b0862b8d
12 changed files with 86 additions and 169 deletions

View File

@ -1,6 +1,7 @@
/* eslint-disable indent, no-process-exit */
const _ = require('lodash');
const { config, Dependencies } = require('@jsdoc/core');
const { Dictionary } = require('jsdoc/tag/dictionary');
const Engine = require('@jsdoc/cli');
const { EventBus, log } = require('@jsdoc/util');
const { Filter } = require('jsdoc/src/filter');
@ -87,6 +88,9 @@ module.exports = (() => {
// Now that we're done loading and merging things, register dependencies.
dependencies.registerValue('config', env.conf);
dependencies.registerValue('options', env.opts);
dependencies.registerSingletonFactory('tags', () =>
Dictionary.fromConfig(dependencies.get('env'))
);
return cli;
};

View File

@ -2,7 +2,6 @@
* @module jsdoc/doclet
*/
const _ = require('lodash');
let dictionary = require('jsdoc/tag/dictionary');
const { isFunction } = require('@jsdoc/parse').astNode;
const {
applyNamespace,
@ -17,7 +16,6 @@ const {
SCOPE_TO_PUNC,
toParts,
} = require('@jsdoc/core').name;
const helper = require('jsdoc/util/templateHelper');
const path = require('path');
const { Syntax } = require('@jsdoc/parse');
const tag = require('jsdoc/tag');
@ -266,20 +264,6 @@ function resolve(doclet) {
}
}
/**
* Replace the existing tag dictionary with a new tag dictionary.
*
* Used for testing only.
*
* @private
* @param {module:jsdoc/tag/dictionary.Dictionary} dict - The new tag dictionary.
*/
exports._replaceDictionary = function _replaceDictionary(dict) {
dictionary = dict;
tag._replaceDictionary(dict);
helper._replaceDictionary(dict);
};
function removeGlobal(longname) {
const globalRegexp = new RegExp(`^${LONGNAMES.GLOBAL}\\.?`);
@ -447,6 +431,7 @@ class Doclet {
* @param {string} [text] - The text of the tag being added.
*/
addTag(title, text) {
const dictionary = this.dependencies.get('tags');
const tagDef = dictionary.lookUp(title);
const newTag = new Tag(title, text, this.meta, this.dependencies);
@ -481,6 +466,8 @@ class Doclet {
* @param {string} longname - The longname for the doclet.
*/
setLongname(longname) {
const dictionary = this.dependencies.get('tags');
/**
* The fully resolved symbol name.
* @type {string}

View File

@ -2,7 +2,6 @@
* Utility functions to support the JSDoc plugin framework.
* @module jsdoc/plugins
*/
const dictionary = require('jsdoc/tag/dictionary');
function addHandlers(handlers, parser, deps) {
Object.keys(handlers).forEach((eventName) => {
@ -11,6 +10,7 @@ function addHandlers(handlers, parser, deps) {
}
exports.installPlugins = (plugins, parser, deps) => {
let dictionary;
let plugin;
for (let pluginModule of plugins) {
@ -24,6 +24,7 @@ exports.installPlugins = (plugins, parser, deps) => {
// ...define tags
if (plugin.defineTags) {
dictionary = deps.get('tags');
plugin.defineTags(dictionary, deps);
}

View File

@ -6,7 +6,6 @@ const _ = require('lodash');
const { log } = require('@jsdoc/util');
const path = require('path');
const tag = {
dictionary: require('jsdoc/tag/dictionary'),
validator: require('jsdoc/tag/validator'),
type: require('@jsdoc/tag').type,
};
@ -119,19 +118,6 @@ function processTagText(tagInstance, tagDef, meta, dependencies) {
}
}
/**
* Replace the existing tag dictionary with a new tag dictionary.
*
* Used for testing only. Do not call this method directly. Instead, call
* {@link module:jsdoc/doclet._replaceDictionary}, which also updates this module's tag dictionary.
*
* @private
* @param {module:jsdoc/tag/dictionary.Dictionary} dict - The new tag dictionary.
*/
exports._replaceDictionary = function _replaceDictionary(dict) {
tag.dictionary = dict;
};
/**
* Represents a single doclet tag.
*/
@ -145,6 +131,7 @@ class Tag {
* @param {object} dependencies
*/
constructor(tagTitle, tagBody, meta, dependencies) {
const dictionary = dependencies.get('tags');
let tagDef;
let trimOpts;
@ -154,9 +141,9 @@ class Tag {
this.originalTitle = trim(tagTitle);
/** The title of the tag (for example, `title` in `@title text`). */
this.title = tag.dictionary.normalize(this.originalTitle);
this.title = dictionary.normalize(this.originalTitle);
tagDef = tag.dictionary.lookUp(this.title);
tagDef = dictionary.lookUp(this.title);
trimOpts = {
keepsWhitespace: tagDef.keepsWhitespace,
removesIndent: tagDef.removesIndent,

View File

@ -1,6 +1,5 @@
/** @module jsdoc/tag/dictionary */
const definitions = require('jsdoc/tag/dictionary/definitions');
const jsdocEnv = require('jsdoc/env');
const { log } = require('@jsdoc/util');
const hasOwnProp = Object.prototype.hasOwnProperty;
@ -10,8 +9,6 @@ const DEFINITIONS = {
jsdoc: 'jsdocTags',
};
let dictionary;
/** @private */
class TagDefinition {
constructor(dict, title, etc) {
@ -173,11 +170,5 @@ class Dictionary {
}
}
// initialize the default dictionary
dictionary = Dictionary.fromConfig(jsdocEnv);
// make the constructor available for unit-testing purposes
dictionary.Dictionary = Dictionary;
/** @type {module:jsdoc/tag/dictionary.Dictionary} */
module.exports = dictionary;
exports.Dictionary = Dictionary;

View File

@ -2,7 +2,6 @@
* @module jsdoc/util/templateHelper
*/
const catharsis = require('catharsis');
let dictionary = require('jsdoc/tag/dictionary');
const { inline } = require('@jsdoc/tag');
const { log } = require('@jsdoc/util');
const { longnamesToTree, SCOPE, SCOPE_TO_PUNC, toParts } = require('@jsdoc/core').name;
@ -42,7 +41,7 @@ const registerId = (exports.registerId = (longname, fragment) => {
linkMap.longnameToId[longname] = fragment;
});
function getNamespace(kind) {
function getNamespace(kind, dictionary) {
if (dictionary.isNamespace(kind)) {
return `${kind}:`;
}
@ -50,8 +49,10 @@ function getNamespace(kind) {
return '';
}
function formatNameForLink(doclet) {
let newName = getNamespace(doclet.kind) + (doclet.name || '') + (doclet.variation || '');
function formatNameForLink(doclet, dependencies) {
const dictionary = dependencies.get('tags');
let newName =
getNamespace(doclet.kind, dictionary) + (doclet.name || '') + (doclet.variation || '');
const scopePunc = SCOPE_TO_PUNC[doclet.scope] || '';
// Only prepend the scope punctuation if it's not the same character that marks the start of a
@ -100,9 +101,11 @@ function makeUniqueFilename(filename, str) {
*
* @function
* @param {string} str The string to convert.
* @param {Object} dependencies The JSDoc dependency container.
* @return {string} The filename to use for the string.
*/
const getUniqueFilename = (exports.getUniqueFilename = (str) => {
const getUniqueFilename = (exports.getUniqueFilename = (str, dependencies) => {
const dictionary = dependencies.get('tags');
const namespaces = dictionary.getNamespaces().join('|');
let basename = (str || '')
// use - instead of : in namespace prefixes
@ -131,13 +134,13 @@ const getUniqueFilename = (exports.getUniqueFilename = (str) => {
* register the filename.
* @private
*/
function getFilename(longname) {
function getFilename(longname, dependencies) {
let fileUrl;
if (hasOwnProp.call(longnameToUrl, longname)) {
fileUrl = longnameToUrl[longname];
} else {
fileUrl = getUniqueFilename(longname);
fileUrl = getUniqueFilename(longname, dependencies);
registerLink(longname, fileUrl);
}
@ -852,9 +855,10 @@ exports.prune = (data, dependencies) => {
* represents a method), the URL will consist of a filename and a fragment ID.
*
* @param {module:jsdoc/doclet.Doclet} doclet - The doclet that will be used to create the URL.
* @param {Object} dependencies - The JSDoc dependency container.
* @return {string} The URL to the generated documentation for the doclet.
*/
exports.createLink = (doclet) => {
exports.createLink = (doclet, dependencies) => {
let fakeContainer;
let filename;
let fileUrl;
@ -875,21 +879,21 @@ exports.createLink = (doclet) => {
// the doclet gets its own HTML file
if (containers.includes(doclet.kind) || isModuleExports(doclet)) {
filename = getFilename(longname);
filename = getFilename(longname, dependencies);
}
// mistagged version of a doclet that gets its own HTML file
else if (!containers.includes(doclet.kind) && fakeContainer) {
filename = getFilename(doclet.memberof || longname);
filename = getFilename(doclet.memberof || longname, dependencies);
if (doclet.name !== doclet.longname) {
fragment = formatNameForLink(doclet);
fragment = formatNameForLink(doclet, dependencies);
fragment = getId(longname, fragment);
}
}
// the doclet is within another HTML file
else {
filename = getFilename(doclet.memberof || exports.globalName);
filename = getFilename(doclet.memberof || exports.globalName, dependencies);
if (doclet.name !== doclet.longname || doclet.scope === SCOPE.NAMES.GLOBAL) {
fragment = formatNameForLink(doclet);
fragment = formatNameForLink(doclet, dependencies);
fragment = getId(longname, fragment);
}
}
@ -912,16 +916,3 @@ exports.createLink = (doclet) => {
* @return {Object} A tree with information about each longname.
*/
exports.longnamesToTree = longnamesToTree;
/**
* Replace the existing tag dictionary with a new tag dictionary.
*
* Used for testing only. Do not call this method directly. Instead, call
* {@link module:jsdoc/doclet._replaceDictionary}, which also updates this module's tag dictionary.
*
* @private
* @param {module:jsdoc/tag/dictionary.Dictionary} dict - The new tag dictionary.
*/
exports._replaceDictionary = function _replaceDictionary(dict) {
dictionary = dict;
};

View File

@ -39,14 +39,14 @@ function getAncestorLinks(doclet) {
return helper.getAncestorLinks(data, doclet);
}
function hashToLink(doclet, hash) {
function hashToLink(doclet, hash, dependencies) {
let url;
if (!/^(#.+)/.test(hash)) {
return hash;
}
url = helper.createLink(doclet);
url = helper.createLink(doclet, dependencies);
url = url.replace(/(#.+|$)/, hash);
return `<a href="${url}">${hash}</a>`;
@ -254,7 +254,7 @@ function generateSourceFiles(sourceFiles, encoding, outdir, dependencies) {
Object.keys(sourceFiles).forEach((file) => {
let source;
// links are keyed to the shortened path in each doclet's `meta.shortpath` property
const sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened);
const sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened, dependencies);
helper.registerLink(sourceFiles[file].shortened, sourceOutfile);
@ -452,10 +452,10 @@ exports.publish = (taffyData, dependencies) => {
// claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
// doesn't try to hand them out later
indexUrl = helper.getUniqueFilename('index');
indexUrl = helper.getUniqueFilename('index', dependencies);
// don't call registerLink() on this one! 'index' is also a valid longname
globalUrl = helper.getUniqueFilename('global');
globalUrl = helper.getUniqueFilename('global', dependencies);
helper.registerLink('global', globalUrl);
// set up templating
@ -490,7 +490,7 @@ exports.publish = (taffyData, dependencies) => {
}
if (doclet.see) {
doclet.see.forEach((seeItem, i) => {
doclet.see[i] = hashToLink(doclet, seeItem);
doclet.see[i] = hashToLink(doclet, seeItem, dependencies);
});
}
@ -595,7 +595,7 @@ exports.publish = (taffyData, dependencies) => {
}
data().each((doclet) => {
let docletPath;
const url = helper.createLink(doclet);
const url = helper.createLink(doclet, dependencies);
helper.registerLink(doclet.longname, url);

View File

@ -1,14 +1,12 @@
const { _replaceDictionary } = require('jsdoc/doclet');
const { augmentAll } = require('jsdoc/augment');
const { createParser } = require('jsdoc/src/parser');
const { Dictionary } = require('jsdoc/tag/dictionary');
const { EventBus } = require('@jsdoc/util');
const fs = require('fs');
const handlers = require('jsdoc/src/handlers');
const path = require('path');
const bus = new EventBus('jsdoc');
let originalDictionaries;
const originalDictionaries = ['jsdoc', 'closure'];
const parseResults = [];
const helpers = {
@ -63,26 +61,20 @@ const helpers = {
},
getParseResults: () => parseResults,
replaceTagDictionary: (dictionaryNames) => {
let dict;
const env = jsdoc.deps.get('env');
const config = jsdoc.deps.get('config');
if (!Array.isArray(dictionaryNames)) {
dictionaryNames = [dictionaryNames];
}
originalDictionaries = env.conf.tags.dictionaries.slice();
env.conf.tags.dictionaries = dictionaryNames;
dict = Dictionary.fromConfig(env);
dict.Dictionary = Dictionary;
_replaceDictionary(dict);
env.conf.tags.dictionaries = originalDictionaries;
config.tags.dictionaries = dictionaryNames;
jsdoc.deps.reset('tags');
},
restoreTagDictionary: () => {
const env = jsdoc.deps.get('env');
const config = jsdoc.deps.get('config');
_replaceDictionary(Dictionary.fromConfig(env));
config.tags.dictionaries = originalDictionaries.slice();
jsdoc.deps.reset('tags');
},
};

View File

@ -2,7 +2,7 @@ const hasOwnProp = Object.prototype.hasOwnProperty;
const options = jsdoc.deps.get('options');
describe('jsdoc/tag', () => {
const jsdocDictionary = require('jsdoc/tag/dictionary');
const jsdocDictionary = jsdoc.deps.get('tags');
const jsdocTag = require('jsdoc/tag');
const parseType = require('@jsdoc/tag').type.parse;

View File

@ -1,6 +1,5 @@
describe('jsdoc/tag/dictionary', () => {
const dictionary = require('jsdoc/tag/dictionary');
const Dictionary = dictionary.Dictionary;
const { Dictionary } = require('jsdoc/tag/dictionary');
const env = jsdoc.deps.get('env');
let testDictionary;
@ -17,56 +16,12 @@ describe('jsdoc/tag/dictionary', () => {
TAG_DEF = testDictionary.defineTag(TAG_TITLE, tagOptions).synonym(TAG_SYNONYM);
});
it('is an instance of dictionary.Dictionary', () => {
expect(dictionary instanceof dictionary.Dictionary).toBe(true);
});
it('has a defineSynonym method', () => {
expect(dictionary.defineSynonym).toBeFunction();
});
it('has a defineTag method', () => {
expect(dictionary.defineTag).toBeFunction();
});
it('has a defineTags method', () => {
expect(dictionary.defineTags).toBeFunction();
});
it('has a fromConfig static method', () => {
expect(dictionary.Dictionary.fromConfig).toBeFunction();
});
it('has a lookup method', () => {
expect(dictionary.lookup).toBeFunction();
});
it('has a lookUp method', () => {
expect(dictionary.lookUp).toBeFunction();
});
it('has an isNamespace method', () => {
expect(dictionary.isNamespace).toBeFunction();
});
it('has a normalise method', () => {
expect(dictionary.normalise).toBeFunction();
});
it('has a normalize method', () => {
expect(dictionary.normalize).toBeFunction();
});
it('has a Dictionary constructor', () => {
expect(dictionary.Dictionary).toBeFunction();
});
describe('defineSynonym', () => {
it('adds a synonym for the specified tag', () => {
dictionary.defineTag('foo', {});
dictionary.defineSynonym('foo', 'bar');
testDictionary.defineTag('foo', {});
testDictionary.defineSynonym('foo', 'bar');
expect(dictionary.normalize('bar')).toBe('foo');
expect(testDictionary.normalize('bar')).toBe('foo');
});
});
@ -274,14 +229,4 @@ describe('jsdoc/tag/dictionary', () => {
expect(testDictionary.normalize(TAG_SYNONYM)).toBe(TAG_DEF.title);
});
});
describe('Dictionary', () => {
it('is a constructor', () => {
function newDictionary() {
return new dictionary.Dictionary();
}
expect(newDictionary).not.toThrow();
});
});
});

View File

@ -15,7 +15,7 @@ describe('jsdoc/tag/validator', () => {
});
describe('validate', () => {
const dictionary = require('jsdoc/tag/dictionary');
const dictionary = jsdoc.deps.get('tags');
const allowUnknown = Boolean(config.tags.allowUnknownTags);
const badTag = { dependencies: jsdoc.deps, title: 'lkjasdlkjfb' };

View File

@ -3,7 +3,8 @@ const hasOwnProp = Object.prototype.hasOwnProperty;
describe('jsdoc/util/templateHelper', () => {
const _ = require('lodash');
const dictionary = require('jsdoc/tag/dictionary');
const { Dependencies } = require('@jsdoc/core');
const { Dictionary } = require('jsdoc/tag/dictionary');
const doclet = require('jsdoc/doclet');
const helper = require('jsdoc/util/templateHelper');
const { taffy } = require('taffydb');
@ -139,31 +140,31 @@ describe('jsdoc/util/templateHelper', () => {
// TODO: needs more tests for unusual values and things that get special treatment (such as
// inner members)
it('should convert a simple string into the string plus the default extension', () => {
const filename = helper.getUniqueFilename('BackusNaur');
const filename = helper.getUniqueFilename('BackusNaur', jsdoc.deps);
expect(filename).toBe('BackusNaur.html');
});
it('should replace slashes with underscores', () => {
const filename = helper.getUniqueFilename('tick/tock');
const filename = helper.getUniqueFilename('tick/tock', jsdoc.deps);
expect(filename).toBe('tick_tock.html');
});
it('should replace other problematic characters with underscores', () => {
const filename = helper.getUniqueFilename('a very strange \\/?*:|\'"<> filename');
const filename = helper.getUniqueFilename('a very strange \\/?*:|\'"<> filename', jsdoc.deps);
expect(filename).toBe('a very strange __________ filename.html');
});
it('should not allow a filename to start with an underscore', () => {
expect(helper.getUniqueFilename('')).toBe('-_.html');
expect(helper.getUniqueFilename('', jsdoc.deps)).toBe('-_.html');
});
it('should not return the same filename twice', () => {
const name = 'polymorphic';
const filename1 = helper.getUniqueFilename(name);
const filename2 = helper.getUniqueFilename(name);
const filename1 = helper.getUniqueFilename(name, jsdoc.deps);
const filename2 = helper.getUniqueFilename(name, jsdoc.deps);
expect(filename1).not.toBe(filename2);
});
@ -171,23 +172,26 @@ describe('jsdoc/util/templateHelper', () => {
it('should not consider the same name with different letter case to be unique', () => {
const camel = 'myJavaScriptIdentifier';
const pascal = 'MyJavaScriptIdentifier';
const filename1 = helper.getUniqueFilename(camel);
const filename2 = helper.getUniqueFilename(pascal);
const filename1 = helper.getUniqueFilename(camel, jsdoc.deps);
const filename2 = helper.getUniqueFilename(pascal, jsdoc.deps);
expect(filename1.toLowerCase()).not.toBe(filename2.toLowerCase());
});
it('should remove variations from the longname before generating the filename', () => {
const filename = helper.getUniqueFilename('MyClass(foo, bar)');
const filename = helper.getUniqueFilename('MyClass(foo, bar)', jsdoc.deps);
expect(filename).toBe('MyClass.html');
});
it('should generate the correct filename for built-in namespaces', () => {
const filenameEvent = helper.getUniqueFilename('event:userDidSomething');
const filenameExternal = helper.getUniqueFilename('external:NotInThisPackage');
const filenameModule = helper.getUniqueFilename('module:some/sort/of/module');
const filenamePackage = helper.getUniqueFilename('package:node-solve-all-your-problems');
const filenameEvent = helper.getUniqueFilename('event:userDidSomething', jsdoc.deps);
const filenameExternal = helper.getUniqueFilename('external:NotInThisPackage', jsdoc.deps);
const filenameModule = helper.getUniqueFilename('module:some/sort/of/module', jsdoc.deps);
const filenamePackage = helper.getUniqueFilename(
'package:node-solve-all-your-problems',
jsdoc.deps
);
expect(filenameEvent).toBe('event-userDidSomething.html');
expect(filenameExternal).toBe('external-NotInThisPackage.html');
@ -196,15 +200,16 @@ describe('jsdoc/util/templateHelper', () => {
});
it('should generate the correct filename for user-specified namespaces', () => {
const deps = new Dependencies();
const dict = new Dictionary();
let filename;
const dict = new dictionary.Dictionary();
dict.defineTag('anaphylaxis', {
isNamespace: true,
});
doclet._replaceDictionary(dict);
deps.registerSingletonFactory('tags', () => dict);
filename = helper.getUniqueFilename('anaphylaxis:peanut');
filename = helper.getUniqueFilename('anaphylaxis:peanut', deps);
expect(filename).toBe('anaphylaxis-peanut.html');
});
@ -1556,6 +1561,7 @@ describe('jsdoc/util/templateHelper', () => {
describe('createLink', () => {
it('should create a url for a simple global.', () => {
const mockDoclet = {
dependencies: jsdoc.deps,
kind: 'function',
longname: 'foo',
name: 'foo',
@ -1568,6 +1574,7 @@ describe('jsdoc/util/templateHelper', () => {
it('should create a url for a namespace.', () => {
const mockDoclet = {
dependencies: jsdoc.deps,
kind: 'namespace',
longname: 'foo',
name: 'foo',
@ -1579,6 +1586,7 @@ describe('jsdoc/util/templateHelper', () => {
it('should create a url for a member of a namespace.', () => {
const mockDoclet = {
dependencies: jsdoc.deps,
kind: 'function',
longname: 'ns.foo',
name: 'foo',
@ -1590,6 +1598,7 @@ describe('jsdoc/util/templateHelper', () => {
});
const nestedNamespaceDoclet = {
dependencies: jsdoc.deps,
kind: 'function',
longname: 'ns1.ns2.foo',
name: 'foo',
@ -1611,6 +1620,7 @@ describe('jsdoc/util/templateHelper', () => {
it('should create a url for a name with invalid characters.', () => {
const mockDoclet = {
dependencies: jsdoc.deps,
kind: 'function',
longname: 'ns1."!"."*foo"',
name: '"*foo"',
@ -1623,6 +1633,7 @@ describe('jsdoc/util/templateHelper', () => {
it('should create a url for a function that is the only symbol exported by a module.', () => {
const mockDoclet = {
dependencies: jsdoc.deps,
kind: 'function',
longname: 'module:bar',
name: 'module:bar',
@ -1634,11 +1645,13 @@ describe('jsdoc/util/templateHelper', () => {
it('should create a url for a doclet with the wrong kind (caused by incorrect JSDoc tags', () => {
const moduleDoclet = {
dependencies: jsdoc.deps,
kind: 'module',
longname: 'module:baz',
name: 'module:baz',
};
const badDoclet = {
dependencies: jsdoc.deps,
kind: 'member',
longname: 'module:baz',
name: 'module:baz',
@ -1652,11 +1665,13 @@ describe('jsdoc/util/templateHelper', () => {
it('should create a url for a function that is a member of a doclet with the wrong kind', () => {
const badModuleDoclet = {
dependencies: jsdoc.deps,
kind: 'member',
longname: 'module:qux',
name: 'module:qux',
};
const memberDoclet = {
dependencies: jsdoc.deps,
kind: 'function',
name: 'frozzle',
memberof: 'module:qux',
@ -1672,6 +1687,7 @@ describe('jsdoc/util/templateHelper', () => {
it('should include the scope punctuation in the fragment ID for static members', () => {
const functionDoclet = {
dependencies: jsdoc.deps,
kind: 'function',
longname: 'Milk.pasteurize',
name: 'pasteurize',
@ -1685,6 +1701,7 @@ describe('jsdoc/util/templateHelper', () => {
it('should include the scope punctuation in the fragment ID for inner members', () => {
const functionDoclet = {
dependencies: jsdoc.deps,
kind: 'function',
longname: 'Milk~removeSticksAndLeaves',
name: 'removeSticksAndLeaves',
@ -1698,6 +1715,7 @@ describe('jsdoc/util/templateHelper', () => {
it('should omit the scope punctuation from the fragment ID for instance members', () => {
const propertyDoclet = {
dependencies: jsdoc.deps,
kind: 'member',
longname: 'Milk#calcium',
name: 'calcium',
@ -1711,6 +1729,7 @@ describe('jsdoc/util/templateHelper', () => {
it('should include the variation, if present, in the fragment ID', () => {
const variationDoclet = {
dependencies: jsdoc.deps,
kind: 'function',
longname: 'Milk#fat(percent)',
name: 'fat',