Skip to content

Instantly share code, notes, and snippets.

@benc-uk
Last active October 26, 2022 11:08
Show Gist options
  • Save benc-uk/1da5e443e3bc0b34bc0346b716c36c19 to your computer and use it in GitHub Desktop.
Save benc-uk/1da5e443e3bc0b34bc0346b716c36c19 to your computer and use it in GitHub Desktop.
Linting

Linting config and tools

Name of Language

Linting

Install

As of .NET 5, this is included in the base SDK

VS Code Configuration

Install the C# extension for VS Code

By default the extensions will not fix or format on save and the linter is not enabled, switch it on with enableRoslynAnalyzers and formatOnSave

{
  "[csharp]": {
      "editor.defaultFormatter": "ms-dotnettools.csharp",
      "editor.formatOnSave": true,
      "editor.codeActionsOnSave": {
          "source.fixAll": true
      }
  },
  

  "omnisharp.enableRoslynAnalyzers": true,
  "omnisharp.useModernNet": true,
  "omnisharp.enableEditorConfigSupport": true,
  "omnisharp.analyzeOpenDocumentsOnly": true
}

CI/CD

name: Example CI for GitHub Actions
on:
  push:
  
jobs:
  linting:
    name: Run code checks
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-dotnet@v2
        with:
          dotnet-version: '6.0.x' # SDK Version to use    
      - uses: actions/checkout@v3

      # Check formatting and style first
      - run: dotnet format --verbosity detailed --verify-no-changes
        working-directory: ./src

      # There's no way to separately run code analysis/linting, the best choice is do a build
      - run: dotnet clean && dotnet build -warnaserror
        working-directory: ./src        

Linting Configuration

The linter should be enabled by default when running a build, however it's probably worth configuring it, to be explicit:

Add to .csproj file

<PropertyGroup>
  <!-- Enable the code analyzers, should be on by default -->
  <EnableNETAnalyzers>true</EnableNETAnalyzers>

  <!-- Not essential if running dotnet format before build -->
  <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>

  <!-- This setting does nothing currently, due to a bug, hope they fix it -->
  <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
</PropertyGroup>

The bulk of configuration should be done in a .editorconfig file. The provided file, is

Example .editorconfig file
root = true
end_of_line = lf

# All files
[*]
indent_style = space

# XML project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2

# Indentation and spacing
# Code files
[*.{cs,csx}]
indent_size = 4
tab_width = 4
insert_final_newline = true
charset = utf-8-bom

###############################
# .NET Coding Conventions     #
###############################
# Organize usings
dotnet_sort_system_directives_first = true
# this. preferences
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent

###############################
# C# Coding Conventions       #
###############################
# var preferences
csharp_style_var_for_built_in_types = true:silent
csharp_style_var_when_type_is_apparent = true:silent
csharp_style_var_elsewhere = true:silent
# Expression-bodied members
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
# Null-checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
# Expression-level preferences
csharp_prefer_braces = true:silent
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion

###############################
# C# Formatting Rules         #
###############################
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true

########################################
# Enable analyzer rules sets           #
# - Switch on everything by default    #
########################################
dotnet_analyzer_diagnostic.category-Design.severity = error
dotnet_analyzer_diagnostic.category-Documentation.severity = error
dotnet_analyzer_diagnostic.category-Globalization.severity = none
dotnet_analyzer_diagnostic.category-Interoperability.severity = error
dotnet_analyzer_diagnostic.category-Maintainability.severity = error
dotnet_analyzer_diagnostic.category-Naming.severity = error
dotnet_analyzer_diagnostic.category-Performance.severity = error
dotnet_analyzer_diagnostic.category-SingleFile.severity = error
dotnet_analyzer_diagnostic.category-Reliability.severity = error
dotnet_analyzer_diagnostic.category-Security.severity = error
dotnet_analyzer_diagnostic.category-Style.severity = error
dotnet_analyzer_diagnostic.category-Usage.severity = error
dotnet_analyzer_diagnostic.category-CodeQuality.severity = error

# This rule often clashes with CA1707 (no underscores)
dotnet_diagnostic.IDE0130.severity = none

Formatting

  • Recommended linter: dotnet format as of .NET 6 this is included in the SDK. The there is technically no separate formatter it's just a subset of the code analysis rules, those beginning with IDExxxx that the format tool will run.
  • Website: https://github.com/dotnet/format
dotnet format --verify-no-changes

Go

Linting

  • Recommended linter: golangci-lint this is a Go linter aggregator and runner, designed with CI in mind
  • Website: https://golangci-lint.run/

Install

curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $HOME/.local/bin

golangci-lint --version

VS Code Configuration

{
  "go.lintTool": "golangci-lint",
  "go.lintOnSave": "file",
}

CI/CD

This linter has a dedicated GitHub Action available https://github.com/golangci/golangci-lint-action. It is suggested you use this action, rather than trying to manually install the binary as part of the workflow and running with bash.

Sample minimal workflow to run this action

name: Example for golangci-lint
on:
  push:
  
jobs:
  linting:
    name: Run linting
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-go@v3
      - uses: actions/checkout@v3
      - uses: golangci/golangci-lint-action@v3

Linting Configuration

As a linter aggregator, golangci-lint has a lot of options and possible linters it can call. See https://golangci-lint.run/usage/linters. It's best to enable linters one at a time observe the effects, some suggestions are provided below.

Example .golangci.yaml file

# =================================================================
# An opinionated config for linting Go code with golangci-lint
# See https://golangci-lint.run/usage/linters 
# =================================================================

linters:
  enable:
    - revive      # Replacement for golint
    - gofmt       # Runs gofmt as part of the linter
    - gosec       # Find security issues
    - wsl         # Whitespace style enforcer, a matter of taste
    - stylecheck  # A few Go style rules
    - nosnakecase # We are not writing Python here
    - misspell    # Find misspelled words
    - cyclop      # Find cyclomatic complexity
    - gocyclo     # Also find cyclomatic complexity
    - bodyclose   # Check for HTTP body close errors
    - nilerr      # Find bad nil/err handling
    - nilnil      # Also find bad nil/err handling
    - tagliatelle # Find badly named struct tags
    #- gomnd       # Find magic numbers, enable at your peril

linters-settings:
  misspell:
    locale: UK # Enable UK spelling

  # Check struck tag naming
  tagliatelle:
    case:
      use-field-name: true
      rules:
        json: goCamel
        yaml: goCamel

  revive:
    severity: error 
    enable-all-rules: false
    confidence: 0.5
    rules:
      # There are MANY rules you could enable...
      # See https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
      # And https://golangci-lint.run/usage/linters/#revive
      - name: line-length-limit
        severity: error
        arguments: [120]

Formatting

  • Recommended formatter: The built-in go fmt which is part of the standard Go language toolchain

Install

NA, it's part of the base Go language tooling

VS Code Configuration

The Go language extension for VS Code will automatically run go fmt on save, but if you wish to explicitly enable it, use the following settings

{
  "go.formatTool": "gofmt",
  "[go]": {
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "source.organizeImports": true
    }
  },
}

CI/CD

Run this formatter as part of golangci-lint as one of the configurable linters it can run (even though strictly speaking it is not a linter!), by adding gofmt to the enabled linters list in the configuration.

If you wish to run the tool directly in a pipeline, be aware it does not set an non-zero exit code when it finds code that needs formatting, a workaround is to use bash as follows

if [[ "$(gofmt -l ./srcdir | wc -l)" -gt 0 ]]; then
    # Output to the logs the code that needs formatting
    gofmt -d ./srcdir
    exit 1
fi

Suggested Configuration

NA, this tool requires no configuration

JavaScript, TypeScript and Node.js

Linting

  • Recommended linter: eslint - ESlint has become the de-facto linter for both JavaScript and TypeScript
  • Website: https://eslint.org/

Install

Install in your project, to be run via npm

npm install eslint --save-dev

Add a script to run the linter in the package.json to run eslint

"scripts": {
  "lint": "eslint ./src",
  "lint-fix": "eslint ./src --fix"
},

VS Code Configuration

Install the ESLint extension

This requires little to no config, and by default will find linting errors as you type.

Should you wish to try to auto fix linting problems when saving files, you can enable it as follows:

"[typescript][javascript]": {
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

CI/CD

name: Example CI for GitHub Actions
on:
  push:

jobs:
  linting:
    name: Run linting
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: "16.x"
          cache: "npm"
      - run: npm ci
      - run: npm run lint

Configuration

ESLint is a sophisticated linter supporting several languages and many runtime environments, as such it's difficult to provide a single suggested config and rule setup. It is recommended you run the interactive configuration creator to get started.

npm init @eslint/config
  • Pick "To check syntax and find problems" when prompted, this will add the eslint:recommended ruleset.
  • If you save the config in JSON format, e.g. .eslintrc.json the VS Code extension can provide intellisense and auto completion.

Suggested rules to switch on are:

"rules": {
  // Use const instead of var or let where possible
  "prefer-const": "error",

  // Disallow use of var for declaring variables
  "no-var": "error",

  // Use arrow functions for callbacks
  "prefer-arrow-callback": "error",

  // Always use `function()` style declarations
  "func-style": ["error", "declaration"],

  // Find pointless return statements
  "no-useless-return": "error",

  // Best practice is not to throw strings for errors
  "no-throw-literal": "error",
}

It's strongly advised to not switch on any rules related purely to formatting (e.g. semi, indent, quotes rules) and leave these concerns to the code formatter Prettier (see below)

There are many publicly available rule sets for ESLint, which can provide an very opinionated set of rules should you wish. Most are collected here - Awesome ESLint Collection. If you use such a ruleset, it is suggested to also use eslint-config-prettier which will prevent any overlap with Prettier by switching off any conflicting rules.

Formatting

  • Recommended formatter: prettier - Prettier is an opinionated code formatter which supports multiple language.
  • Website: https://prettier.io/

Prettier has only a few options & rules, which is a big part of it's strength. It can also format HTML, CSS, Markdown and YAML

If you are curious as to why separating formatting from linting is good idea toy can read this comparison

Install

Install in your project, to be run via npm

npm install prettier --save-dev

Extend the linting script in package.json to also run prettier

"scripts": {
  "lint": "eslint ./src && prettier --check ./src",
  "lint-fix": "eslint ./src --fix && prettier --write ./src"
}

VS Code Configuration

Install the Prettier extension

To have prettier run & format code automatically set it as the default formatter

"[typescript][javascript]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true
}

CI/CD

Run via npm, see linter above

Suggested Configuration

Example .prettierrc, obviously many of these settings come down to personal preference

{
  "semi": false,
  "singleQuote": true,
  "useTabs": false,
  "printWidth": 120,
  "endOfLine": "lf",
  "trailingComma": "none",
  "arrowParens": "always",
  "bracketSameLine": true
}

Name of Language

Linting

  • Recommended linter:
  • Website:

Install

some commands

VS Code Configuration

{
  "blah": "blah"
}

CI/CD

name: Example CI for GitHub Actions
on:
  push:
  
jobs:
  linting:
    name: Run linting
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-go@v3
      - uses: actions/checkout@v3
        # Rest of the workflow here

Linting Configuration

some config

Formatting

  • Recommended formatter:
  • Website:

Install

Blah

VS Code Configuration

{
  // If required
}

CI/CD

Blah

Suggested Configuration

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