diff --git a/CODING_STANDARDS.md b/CODING_STANDARDS.md index 16badedbe0f..5009fbee2b7 100644 --- a/CODING_STANDARDS.md +++ b/CODING_STANDARDS.md @@ -12,7 +12,8 @@ ES6 or TypeScript. ### General #### Write useful comments -Comments that explain what some block of code does are nice; they can tell you something in less time than it would take to follow through the code itself. +Comments that explain what some block of code does are nice; they can tell you something in less +time than it would take to follow through the code itself. Comments that explain why some block of code exists at all, or does something the way it does, are _invaluable_. The "why" is difficult, or sometimes impossible, to track down without seeking out @@ -58,19 +59,122 @@ Keeping modules to a single responsibility makes the code easier to test, consum ES6 modules offer a straightforward way to organize code into logical, granular units. Ideally, individual files are 200 - 300 lines of code. +As a rule of thumb, once a file draws near 400 lines (barring abnormally long constants / comments), +start considering how to refactor into smaller pieces. + #### Less is more Once a feature is released, it never goes away. We should avoid adding features that don't offer high user value for price we pay both in maintenance, complexity, and payload size. When in doubt, leave it out. This applies especially so to providing two different APIs to accomplish the same thing. Always -prefer sticking to a _single_ API for accomplishing something. +prefer sticking to a _single_ API for accomplishing something. + +### 100 column limit +All code and docs in the repo should be 100 columns or fewer. This applies to TypeScript, SCSS, + HTML, bash scripts, and markdown files. ### TypeScript -#### Provide function descriptions -For functions that are more complicated than a simple getter/setter, provide at least a brief -sentence explaining what the function does and/or _why_ it does something. +#### Typing +Avoid `any` where possible. If you find yourself using `any`, consider whether a generic may be +appropriate in your case. + +For methods and properties that are part of a component's public API, all types must be explicitly +specified because our documentation tooling cannot currently infer types in places where TypeScript +can. + +#### Fluent APIs +When creating a fluent or builder-pattern style API, use the `this` return type for methods: +``` +class ConfigBuilder { + withName(name: string): this { + this.config.name = name; + return this; + } +} +``` + +#### Access modifiers +* Omit the `public` keyword as it is the default behavior. +* Use `private` when appropriate and possible, prefixing the name with an underscore. +* Use `protected` when appropriate and possible with no prefix. +* Prefix *library-internal* properties and methods with an underscore without using the `private` +keyword. This is necessary for anything that must be public (to be used by Angular), but should not +be part of the user-facing API. This typically applies to symbols used in template expressions, +`@ViewChildren` / `@ContentChildren` properties, host bindings, and `@Input` / `@Output` properties +(when using an alias). + +Additionally, the `@docs-private` JsDoc annotation can be used to hide any symbol from the public +API docs. + +#### JsDoc comments + +All public APIs must have user-facing comments. These are extracted and shown in the documation +on [material.angular.io](https://material.angular.io). + +Private and internal APIs should have JsDoc when they are not obvious. Ultimately it is the purview +of the code reviwer as to what is "obvious", but the rule of thumb is that *most* classes, +properties, and methods should have a JsDoc description. + +Properties should have a concise description of what the property means: +```ts + /** The label position relative to the checkbox. Defaults to 'after' */ + @Input() labelPosition: 'before' | 'after' = 'after'; +``` + +Methods blocks should describe what the function does and provide a description for each parameter +and the return value: +```ts + /** + * Opens a modal dialog containing the given component. + * @param component Type of the component to load into the dialog. + * @param config Dialog configuration options. + * @returns Reference to the newly-opened dialog. + */ + open(component: ComponentType, config?: MdDialogConfig): MdDialogRef { ... } +``` + +Boolean properties and return values should use "Whether..." as opposed to "True if...": +```ts + /** Whether the button is disabled. */ + disabled: boolean = false; +``` + +#### Naming + +##### General +* Prefer writing out words instead of using abbreviations. +* Prefer *exact* names over short names (within reason). E.g., `labelPosition` is better than +`align` because the former much more exactly communicates what the property means. +* Except for `@Input` properties, use `is` and `has` prefixes for boolean properties / methods. + +##### Classes +Classes should be named based on what they're responsible for. Names should capture what the code +*does*, not how it is used: +``` +/** NO: */ +class RadioService { } + +/** YES: */ +class UniqueSelectionDispatcher { } +``` + +Avoid suffixing a class with "Service", as it communicates nothing about what the class does. Try to +think of the class name as a person's job title. + +##### Methods +The name of a method should capture the action that is performed *by* that method. + +### Angular + +#### Host bindings +Prefer using the `host` object in the directive configuration instead of `@HostBinding` and +`@HostListener`. We do this because TypeScript preserves the type information of methods with +decorators, and when one of the arguments for the method is a native `Event` type, this preserved +type information can lead to runtime errors in non-browser environments (e.g., server-side +pre-rendering). + ### CSS @@ -83,8 +187,8 @@ elements like input and button. #### Use lowest specificity possible Always prioritize lower specificity over other factors. Most style definitions should consist of a -single element or css selector plus necessary state modifiers. Avoid SCSS nesting for the sake of -code organization. This will allow users to much more easily override styles. +single element or css selector plus necessary state modifiers. **Avoid SCSS nesting for the sake of +code organization.** This will allow users to much more easily override styles. For example, rather than doing this: ```scss @@ -123,7 +227,7 @@ The end-user of a component should be the one to decide how much margin a compon This makes it easier to override styles when necessary. For example, rather than ```scss -:host { +the-host-element { // ... .some-child-element { @@ -134,7 +238,7 @@ This makes it easier to override styles when necessary. For example, rather than you can write ```scss -:host { +the-host-element { // ... color: red; }