Skip to content

Instantly share code, notes, and snippets.

@codfish
Last active November 14, 2023 16:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codfish/d9f82330efcbc429222112ffcf3b61ba to your computer and use it in GitHub Desktop.
Save codfish/d9f82330efcbc429222112ffcf3b61ba to your computer and use it in GitHub Desktop.
Documenting your code

Documenting Your Code

This is a detailed spec on how developers should be documenting their code. While inline code comments are very important, this specification will be focusing on docblock structure.

Docblocks are required for all class methods & properties/attributes. The reason we have this spec, and the reason that actual language standards exist for docblocks, is so that generators will know how to parse your code & render consumable html documentation.

For this reason, documentation standards follow the existing spec's that are currently community standards at the time of writing, and we should be following them.

There is a lot of overlap in each spec, so this wiki doc will serve as the reference for both languages. If something is PHP or JS specific, it will be noted.

#TL;DR: Comprehensive Examples

Table of Contents

  1. Definitions
  2. Comprehensive Examples
  3. General Rules
  4. Tech Debt: Things we've done wrong
  5. Code Examples in Docblocks
  6. Nested Parameters
  7. Types
  8. Default & Optional Parameters
  9. Editor Integration
  10. Resources

Definitions

Docblock

This is a section of documentation which provides information on several aspects of Structural Elements.

Summary

This is what comes first in every docblock. The short description of what a method/variable is. JS example:

/**
 * This is a summary. It should always be the first thing in a
 * docblock. It should **NEVER** span more than two lines.
 */
Description

This is a more detailed explanation than the summary. It is optional, and if included, it should always come after the summary. JS example:

/**
 * This is a summary. It should always be the first thing in a
 * docblock. It should **NOT** span more than two lines.
 *
 * This is the description. Typically only included when you have
 * more you want to say about the method/variable you're documenting
 * that can't fit in the 2-line summary.
 *
 * Descriptions can be multiple paragraphs, can include *markdown*,
 * headers, code examples `NOTE:`'s, and other things.
 *
 * ### Examples
 *
 * Example of code block in JS:
 * ```
 * var foo = _.map( this.fooBar );
 * ```
 */
Tags

The technical term for the @ descriptors you use in docblocks. Common ones we all know are @param & @return. A @param tag in a docblock are made up of the tag, a type, a variable name, and a tag description.

Here's a JS example: * @param {number} input Any number. Where:

  • @param is a tag: there are many tags, and they all begin with the @ symbol.
  • {number} is a type. It says that the input to this function needs to be a JavaScript "number" type. It could also say string, like {string}, {object}, {Date}, or any other JavaScript built-in type. And if you defined a custom class, like FooClass, you can use it as a type too by saying {FooClass}.
  • input is the name of the input variable. It matches what the code says right below it (function addOne(input)).
  • any number is the description of the input.
Docblock section

Term referring to the Summary, Description, and different groups of tags that comprise the docblock. Each section should be separated by a line break. JS example:

/**
 * Counter class constructor.
 *
 * @see {@link DialogModal#show} for full list of options.
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/alert}
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/dialog}
 *
 * @throws {InvalidArgumentException} Optional @throws description
 * @throws {DivideByZero} Argument x must be non-zero.
 * @throws Will throw an error if the argument is null.
 *
 * @param {jQuery} textField - This is a tag description.
 * @param {jQuery} [$counterEl] - This is another tag description.
 * @param {string} [maxGlue] - This is the last `@param` tag description.
 * @return {Counter} The is the `@return` tag description.
 */

Comprehensive Examples

JS

/**
 * Show the dialog modal.
 *
 * NOTE: you can call this method directly, but would typically
 * call the `alert()` or `confirm()` aliases directly instead.
 *
 * Show a confirmation modal with custom html template content:
 *
 * ```
 * DialogModal.show( {
 *     onConfirm: this.onAchknowledgeError.bind( this ) );
 *     content: _.template( $( "#error-template" ).html() )( data),
 *     error: true,
 *     title: "error-title",
 *     type: "confirm",
 * } );
 * ```
 *
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/alert}
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/dialog}
 *
 * @todo Add unit tests.
 * @todo Add notes in readme.
 *
 * @throws {HttpException} If resource creation fails, throws an exception.
 * @throws {CustomException} If resource query fails, throws an exception.
 *
 * @param {string} reqParam - Required parameters shouldn't have [ ] around them
 * @param {object} [options={}] - Options to pass to show
 * @param {function} [options.cancel] - Callback that executes when user clicks cancel button.
 * @param {string|boolean} [options.cancelButton="cancel-button"] - Cancel button message key. Hides button when false.
 * @param {function} [options.confirm] - Callback that executes when user clicks confirm button.
 * @param {string|boolean} [options.confirmButton="confirm-button"] - Confirm button message key.
 * @param {string} [options.content=""] - Text/html displayed in the dialog.
 * @param {boolean} [options.error=false] - When true, add's error class, and drives error styling.
 * @param {string} [options.title="default-alert-title"] - Translation message key for dialog title bar.
 * @param {boolean} [options.type="alert"] - Type of dialog modal it is. Options are "alert" or "confirm".
 * @return {ConfirmModal} Returns a reference to `this` to allow for chaining
 */
show: function( reqParam, options ) {}

PHP

/**
 * Attempt to use a form model to clone a resource. Abstracted
 * here to keep controller actions more DRY.
 *
 * - list item 1
 * - list item 2
 * - list **item** 3
 *
 * 1. Ordered list item 1
 * 1. Ordered list item 2
 * 1. Ordered list _item_ 3
 *
 * NOTE: Only use this method in controllers.
 *
 * Using this method correctly from a controller:
 *
 *     $model = ContentModel::model()->findOne( $id );
 *
 *     // Use shared method to clone the resource
 *     return $this->cloneResource([
 *         "resourceName" => "content",
 *         "formModelName" => "app\\formmodels\content\ContentCloneFormModel",
 *         "editRoutePrefix" => "content/edit",
 *         "item" => $model,
 *     ]);
 *
 * @todo Add unit tests.
 * @todo Add notes in readme.
 *
 * @throws HttpException If resource creation fails, throws an exception.
 * @throws CustomException If resource query fails, throws an exception.
 *
 * @param SiteModel $site - The site we're cloning from.
 * @param bool $isSyn - Whether its syndicated.
 * @param mixed[] $options <pre>{
 *     @var string $resourceName - The name of the resource.
 *                                 NOTE: Used for error message generation, so should be singular.
 *                                 Ex: "tag", "content", "collection", etc.
 *     @var string $formModelName - The form model name to be used.
 *                                  NOTE: This should be a fully-namespaced string.
 *     @var string $namespace - The form model namespace to be used.
 *     @var string $editRoutePrefix - The prefix to a model's edit route.
 *                                    NOTE: A prefix should not have a trailing slash.
 *     @var CoreModel $item - The item to be cloned.
 * }</pre>
 *
 * @return mixed When a resource is cloned successfully, returns a redirect to the edit page.
 */
public function cloneResource($site, $isSyn, $options = []) {}

General Rules

  • Only use spaces in docblocks! DON'T use tabs.
  • Don't bother lining up tag names & descriptions of @param & @return tags. It's hard to enforce.
  • Use dashes between the field name and description

Bad:

/**
 * Counter class constructor.
 *
 * @param  {jQuery}  $textField   This is a tag description.
 * @param  {jQuery}  [$counterEl] This is another tag description.
 * @return {Counter}              The is the `@return` tag description.
 */

^ This looks good but can become unweildy and annoying, and most importantly super annoying to lint/enforce amongst the team.

Good:

/**
 * Counter class constructor.
 *
 * @param {jQuery} $textField - This is a tag description.
 * @param {jQuery} [$counterEl] - This is another tag description.
 * @return {Counter} The is the `@return` tag description.
 */
  • Different docblock "sections" should be separated by a line break.

Bad:

/**
 * Alias for show(), with some default options set.
 * NOTE: This is a longer more detailed description block.
 * @see {@link DialogModal#show} for full list of options.
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/alert}
 * @param {object|string} options Modal options object. If string, it represents the modal's content.
 * @param {function} [callback] If `options` param is a string, this will be used as the confirm callback.
 * @return {ConfirmModal} Returns a reference to `this` to allow for chaining
 */

Good:

/**
 * Alias for show(), with some default options set.
 *
 * NOTE: This is a longer more detailed description block.
 *
 * @see {@link DialogModal#show} for full list of options.
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/alert}
 *
 * @param {object|string} options - Modal options object. If string, it represents the modal's content.
 * @param {function} [callback] - If `options` param is a string, this will be used as the confirm callback.
 * @return {ConfirmModal} Returns a reference to `this` to allow for chaining.
 */
  • @param and @return tags should always be last in the docblock if they exist. All other tags, e.g. @throws, @see, @author, @todo, etc. should come between the docblock summary/description and the @param's. JS Example:

Bad:

/**
 * Alias for show(), with some default options set.
 *
 * NOTE: This is a longer more detailed description block.
 *
 * @param {object|string} options Modal options object. If string, it represents the modal's content.
 *
 * @return {ConfirmModal} Returns a reference to `this` to allow for chaining.
 *
 * @todo Add unit tests :).
 *
 * @see {@link DialogModal#show} for full list of options.
 */

Good:

/**
 * Alias for show(), with some default options set.
 *
 * NOTE: This is a longer more detailed description block.
 *
 * @see {@link DialogModal#show} for full list of options.
 *
 * @todo Add unit tests :).
 *
 * @param {object|string} options - Modal options object. If string, it represents the modal's content.
 * @return {ConfirmModal} Returns a reference to `this` to allow for chaining.
 */
  • Use @return, not @returns. @return is supported by both languages.

Bad:

/**
 * @returns {boolean}
 */

Good:

/**
 * @return {boolean}
 */
  • Always include a single space between tag columns, and a dash padded with spaces between var name and summary.

Bad:

/**
 * @param  {jQuery}   textField    This is a tag description.
 */

Good:

/**
 * @param {jQuery} textField - This is a tag description.
 */
  • Always start summaries, descriptions, and tag descriptions with a capital letter & end with a period, or some other punctuation (OPTIONAL, but RECOMMENDED). You have to remember, this is meant to be consumed in the generated html documentation in the browser, not in code! It should read like english to improve usability.

Bad:

/**
 * counter class constructor
 *
 * @param {jQuery} textField - this is a tag description
 */

Good:

/**
 * Counter class constructor.
 *
 * @param {jQuery} textField - This is a tag description.
 */

Tech Debt

Things we've done wrong in the past, that should be changed:

  • Stop repeating function name in docblock summaries, it's repetitive and doesn't read well in documentation. Generated docs display function names right next to their respective summaries.
  • STOP USING TABS. Stop using tabs in docblocks.
  • Use markdown where it applies. Both parsers support Markdown, and in turn they also support HTML in docblocks, if you need them. Note that JSDoc supports Github Flavored Markdown, however Sami (PHP) does not.
  • Writing docblocks only thinking about how they look in code, and not how they'll look in the actual parsed documentation. Just cause you added a line break doesn't make it a new sentence. Parsers ignore single line returns, so if you want separate paragraphs be sure to add line breaks between them. Write full sentences, INCLUDING punctuation!
  • We haven't been adding docblocks to most of our JS code.

Code Examples in Docblocks

It's extremely helpful for reference purposes to include examples in your docblocks. It's important to note, there's multiple different ways to do this in both PHP & JS, but we'll show you the recommended & acceptable ways of doing it here.

JS

  • JSDoc supports Github Flavored Markdown, so it you want to use triple backticks, that is acceptable.
  • Standard markdown convention is to indent code blocks. That is also acceptable. Be sure to use 4 spaces if you use this method.
  • While the @example tag will work fine, it's suggested you use one of the other 2 methods instead. The @example tag is used in PHPDoc as well, but in a different manner, so it can cause confusion between languages.

Good:

/**
 * Indented, normal Markdown:
 *
 *     var foo = _.map( this.fooBar );
 */

Acceptable:

/**
 * Example of code block in JS using Github Flavored Markdown:
 *
 * ```
 * var foo = _.map( this.fooBar );
 * ```
 */

Bad:

/**
 * Indented, normal Markdown. INDENTED CODE BLOCKS NEED A LINE BREAK BEFORE THEM!
 *     var foo = _.map( this.fooBar );
 */

Not Recommended:

/**
 * @example
 * var foo = _.map( this.fooBar );
 */

PHP

  • It's recommended to use the standard Markdown approach and just indent your code blocks 4 spaces.
  • The PHP parser we use does not support Github Flavored Markdown.
  • Indendation does not work for @param tags, so <pre> tags are necessary when styling nested parameters.

Good:

/**
 * Indented, normal Markdown:
 *
 *     array_map( $foo, fooBar );
 */

Acceptable:

/**
 * Example of code block in PHP (doesn't support Github Flavored Markdown):
 * <pre>
 * array_map( \$foo, fooBar );
 * </pre>
 */

Nested Parameters

It's nice knowing that a specific method parameter is an array/object, but what about the properties/attributes of that parameter? Sometimes it's very helpful to include details about "nested parameters" when you're passing in an array or object into a method. JS & PHP have very different ways of handling this.

PHP

PHPDoc's spec details a feature called Inline PHPDoc which is an extremely clean way to document nested parameters.

HOWEVER, none of the PHP doc parsers support this yet unfortunately.

/**
 * Initializes this class with the given options.
 *
 * @param mixed[] \$options {
 *     @var bool   $required Whether this element is required.
 *     @var string $label    The display name for this element.
 * }
 */

SO...I've found that the only way to handle this is to wrap the Inline PHPDoc with <pre> tags in order to preserve line breaks. For example:

/**
 * Initializes this class with the given options.
 *
 * @param mixed[] \$options <pre>{
 *     @var bool   $required Whether this element is required.
 *     @var string $label    The display name for this element.
 * }</pre>
 */

A nice perk of this method is that once the parsers do start to support "Inline PHPDoc", then it will be simple to remove the <pre> tags.

JS

JSDoc takes a different approach to parameters with properties. All the properties should be listed as their own @param's with the parent object prepended in the variable name, i.e. options.nestedProperty.

See: http://usejsdoc.org/tags-param.html#parameters-with-properties

Example:

/**
 * Show the dialog modal.
 *
 * @param {object} [options={}] - Modal options.
 * @param {function} [options.onCancel] - Callback that executes when user clicks cancel button.
 * @param {string|boolean} [options.cancelButton="cancel-button"] - Cancel button message key. Hides button when false.
 * @param {function} [options.onConfirm] - Callback that executes when user clicks confirm button.
 * @param {string|boolean} [options.confirmButton="confirm-button"] - Confirm button message key.
 * @param {string} [options.content=""] - Text/html displayed in the dialog.
 * @param {boolean} [options.error=false] - When true, add's error class, and drives error styling.
 * @param {string} [options.title="default-alert-title"] - Translation message key for dialog title bar.
 * @param {boolean} [options.type="alert"] - Type of dialog modal it is. Options are "alert" or "confirm".
 * @return {ConfirmModal} Returns a reference to `this` to allow for chaining
 */
show: function(options) {}

Types

* @param {string} input - Any string.

^^ In this example, string refers to the "type". Here are the different types you should be using for PHP & JS.

PHP

See: https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md#keyword

  1. string, the element to which this type applies is a string of binary characters.
  2. int, the element to which this type applies is a whole number or integer.
  3. bool, the element to which this type applies only has state TRUE or FALSE.
  4. float, the element to which this type applies is a continuous, or real, number.
  5. object, the element to which this type applies is the instance of an undetermined class.
  6. mixed, the element to which this type applies can be of any type as specified here. It is not known on compile time which type will be used.
  7. array, the element to which this type applies is an array of values.
  8. resource, the element to which this type applies is a resource per the definition of PHP.
  9. void, this type is commonly only used when defining the return type of a method or function.
  10. null, the element to which this type applies is a NULL value or, in technical terms, does not exist.
  11. callable, the element to which this type applies is a pointer to a function call.
  12. false or true, the element to which this type applies will have the value TRUE or FALSE.
  13. self, the element to which this type applies is of the same class as which the documented element is originally contained.
  14. static, the element to which this type applies is of the same class as which the documented element is contained, or when encountered in a subclass is of type of that subclass instead of the original class.
  15. $this, the element to which this type applies is the same exact instance as the current class in the given context. NOTE: If your method takes or returns an instance of a specific class, you should specify the class name as the type.

JS

See: http://usejsdoc.org/tags-type.html

NOTE: It's recommended that you lowercase any primitive types (boolean, null, undefined, number, string), and capitalize other types.

  1. string
  2. number
  3. boolean
  4. mixed
  5. null
  6. undefined
  7. Array - It's preferred that you use one of the following, rather than just Array • mixed[] - array of mixed types • string[] - array of strings • number[] - array of numbers • array[] - array of arrays • object[] - array of objects • MyClass[] - array of type MyClass • etc.
  8. object - For object literals, NOT instance objects. If you want to provide more details about the object properties, you can in the format: object.<string, number>, which would represent an object with string keys and number values. NOTE: If your method takes or returns an instance of a specific class, you should specify the class name as the type, e.g. jQuery, Promise, Element, Event, etc.

Default & Optional Parameters

PHP: You do not need to add anything to the docblock in order to denote optional params or default values. This is because in PHP, you can specify . JS: Because parameter defaults are only supported in recent versions of JS, and Javascript won't throw an error if you don't include parameters anyway, JSDoc expects you to denote optional & defaults in the docblock.

See http://usejsdoc.org/tags-param.html#optional-parameters-and-default-values.

JS Example:

Optional param's have their var names wrapped in square brackets. Defaults get specified following the variable name and an equals sign.

/**
 * @param {jQuery} textField - This is a REQUIRED parameter.
 * @param {string} bar="" - This param is required, with an empty string default `""`.
 * @param {jQuery} [$counterEl] - This is an OPTIONAL parameter. Denoted by square brackets.
 * @param {number} [max=50] - This param is optional, with a default of `50`.
 * @param {boolean} [shown=false] - This boolean param is optional, with a default of `false`.
 * @param {string} [foo="Foo Bar"] - This param is optional, with a default of `"Foo Bar"`.
 */

Editor Integration

Writing Docblocks in SublimeText

Install the Docblockr plugin. Also, in order to conform to our standards, you should overwrite a couple of default settings for the plugin. Just put the following in the Docblockr user settings file.

{
  // Since there seems to be no agreed standard for "@return" or "@returns", use this setting to rename it as you wish.
  "jsdocs_return_tag": "@return",

  // whether the words following the @tags should align.
  // Possible values are 'no', 'shallow', 'deep'
  // For backwards compatibility, false is equivalent to 'no', true is equivalent to 'shallow'
  //
  // 'shallow' will just align the first words after the tag. eg:
  // @param    {MyCustomClass} myVariable desc1
  // @return   {string} foo desc2
  // @property {number} blahblah desc3
  //
  // 'deep' will align each component of the tags, eg:
  // @param    {MyCustomClass} myVariable desc1
  // @return   {string}        foo        desc2
  // @property {number}        blahblah   desc3
  "jsdocs_align_tags": "deep",

  // Whether there should be blank lines added between the description line, and between tags of different types.
  // Possible values are true, false, or "after_description".
  // If true, the output might look like this:
  //
  // /**
  //  * [description]
  //  *
  //  * @param  {string} foo
  //  * @param  {number} bar
  //  *
  //  * @return {[Type]}
  //  */
  //
  // If "after_description" is configured, a blank line is only added between the description and the first tag, but not
  // between different tag sections, so the output, in that case, might look like this:
  //
  // /**
  //  * [description]
  //  *
  //  * @param  {string} foo
  //  * @param  {number} bar
  //  * @return {[Type]}
  //  */
  "jsdocs_spacer_between_sections": true,

  // Whether each section should be indented to the same level, or indent each one individually.
  // (When true, the @param section will lose the extra space immediately after each '@param').
  "jsdocs_per_section_indent": true
}

Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment