diff --git a/server/CHANGELOG.md b/server/CHANGELOG.md index c2123849d..0e2c405ae 100644 --- a/server/CHANGELOG.md +++ b/server/CHANGELOG.md @@ -1,5 +1,9 @@ # Bash Language Server +## 5.1.2 + +- Use shellcheck's shell directive for selecting the dialect https://github.com/bash-lsp/bash-language-server/pull/1081 + ## 5.1.1 - Add --help fallback for documentation https://github.com/bash-lsp/bash-language-server/pull/1052 diff --git a/server/package.json b/server/package.json index 1379d1dcc..b48b6f41f 100644 --- a/server/package.json +++ b/server/package.json @@ -3,7 +3,7 @@ "description": "A language server for Bash", "author": "Mads Hartmann", "license": "MIT", - "version": "5.1.1", + "version": "5.1.2", "main": "./out/server.js", "typings": "./out/server.d.ts", "bin": { diff --git a/server/src/__tests__/__snapshots__/server.test.ts.snap b/server/src/__tests__/__snapshots__/server.test.ts.snap index 665043e70..87f13902a 100644 --- a/server/src/__tests__/__snapshots__/server.test.ts.snap +++ b/server/src/__tests__/__snapshots__/server.test.ts.snap @@ -906,6 +906,7 @@ exports[`server onRenameRequest Workspace-wide rename returns correct WorkspaceE }, }, ], + "file://__REPO_ROOT_FOLDER__/testing/fixtures/shellcheck/shell-directive.bash": [], "file://__REPO_ROOT_FOLDER__/testing/fixtures/shellcheck/source.sh": [], "file://__REPO_ROOT_FOLDER__/testing/fixtures/shellcheck/sourced.sh": [], "file://__REPO_ROOT_FOLDER__/testing/fixtures/sourcing.sh": [], @@ -997,6 +998,7 @@ exports[`server onRenameRequest Workspace-wide rename returns correct WorkspaceE }, }, ], + "file://__REPO_ROOT_FOLDER__/testing/fixtures/shellcheck/shell-directive.bash": [], "file://__REPO_ROOT_FOLDER__/testing/fixtures/shellcheck/source.sh": [], "file://__REPO_ROOT_FOLDER__/testing/fixtures/shellcheck/sourced.sh": [], "file://__REPO_ROOT_FOLDER__/testing/fixtures/sourcing.sh": [], diff --git a/server/src/__tests__/analyzer.test.ts b/server/src/__tests__/analyzer.test.ts index de99646e1..f8e4c0320 100644 --- a/server/src/__tests__/analyzer.test.ts +++ b/server/src/__tests__/analyzer.test.ts @@ -16,7 +16,7 @@ import { Logger } from '../util/logger' const CURRENT_URI = 'dummy-uri.sh' // if you add a .sh file to testing/fixtures, update this value -const FIXTURE_FILES_MATCHING_GLOB = 17 +const FIXTURE_FILES_MATCHING_GLOB = 18 const defaultConfig = getDefaultConfiguration() diff --git a/server/src/util/__tests__/shebang.test.ts b/server/src/util/__tests__/shebang.test.ts index c1e6d07f8..abadf7e99 100644 --- a/server/src/util/__tests__/shebang.test.ts +++ b/server/src/util/__tests__/shebang.test.ts @@ -1,3 +1,4 @@ +import { FIXTURE_DOCUMENT } from '../../../../testing/fixtures' import { analyzeShebang } from '../shebang' describe('analyzeShebang', () => { @@ -35,4 +36,28 @@ describe('analyzeShebang', () => { expect(analyzeShebang(command).shellDialect).toBe(expectedDialect) expect(analyzeShebang(`${command} `).shellDialect).toBe(expectedDialect) }) + + it('returns shell dialect from shell directive', () => { + expect(analyzeShebang('# shellcheck shell=dash')).toEqual({ + shellDialect: 'dash', + shebang: null, + }) + }) + + it('returns shell dialect when multiple directives are passed', () => { + expect( + analyzeShebang( + '# shellcheck enable=require-variable-braces shell=dash disable=SC1000', + ), + ).toEqual({ + shellDialect: 'dash', + shebang: null, + }) + }) + + it('shell directive overrides file extension and shebang', () => { + expect( + analyzeShebang(FIXTURE_DOCUMENT.SHELLCHECK_SHELL_DIRECTIVE.getText()), + ).toHaveProperty('shellDialect', 'sh') + }) }) diff --git a/server/src/util/shebang.ts b/server/src/util/shebang.ts index 13b7e55df..3d5163497 100644 --- a/server/src/util/shebang.ts +++ b/server/src/util/shebang.ts @@ -5,6 +5,10 @@ const SHELL_REGEXP = /bin[/](?:env )?(\w+)/ const BASH_DIALECTS = ['sh', 'bash', 'dash', 'ksh', 'zsh', 'csh', 'ash'] as const type BashDialect = (typeof BASH_DIALECTS)[number] +const SHELL_DIRECTIVE_REGEXP = new RegExp( + `^\\s*#\\s*shellcheck.*shell=(${BASH_DIALECTS.join('|')}).*$|^\\s*#.*$|^\\s*$`, +) + export function getShebang(fileContent: string): string | null { const match = SHEBANG_REGEXP.exec(fileContent) if (!match || !match[1]) { @@ -26,6 +30,25 @@ export function getShellDialect(shebang: string): BashDialect | null { return null } +export function getShellDialectFromShellDirective( + fileContent: string, +): BashDialect | null { + const contentLines = fileContent.split('\n') + for (const line of contentLines) { + const match = SHELL_DIRECTIVE_REGEXP.exec(line) + if (match === null) { + break + } + if (match[1]) { + const bashDialect = match[1].trim() as any + if (BASH_DIALECTS.includes(bashDialect)) { + return bashDialect + } + } + } + return null +} + export function analyzeShebang(fileContent: string): { shellDialect: BashDialect | null shebang: string | null @@ -33,6 +56,8 @@ export function analyzeShebang(fileContent: string): { const shebang = getShebang(fileContent) return { shebang, - shellDialect: shebang ? getShellDialect(shebang) : null, + shellDialect: + getShellDialectFromShellDirective(fileContent) ?? + (shebang ? getShellDialect(shebang) : null), } } diff --git a/testing/fixtures.ts b/testing/fixtures.ts index 9c1ed3842..009174836 100644 --- a/testing/fixtures.ts +++ b/testing/fixtures.ts @@ -29,6 +29,11 @@ export const FIXTURE_URI = { PARSE_PROBLEMS: `file://${path.join(FIXTURE_FOLDER, 'parse-problems.sh')}`, SCOPE: `file://${path.join(FIXTURE_FOLDER, 'scope.sh')}`, SHELLCHECK_SOURCE: `file://${path.join(FIXTURE_FOLDER, 'shellcheck', 'source.sh')}`, + SHELLCHECK_SHELL_DIRECTIVE: `file://${path.join( + FIXTURE_FOLDER, + 'shellcheck', + 'shell-directive.bash', + )}`, SOURCING: `file://${path.join(FIXTURE_FOLDER, 'sourcing.sh')}`, SOURCING2: `file://${path.join(FIXTURE_FOLDER, 'sourcing2.sh')}`, RENAMING: `file://${path.join(FIXTURE_FOLDER, 'renaming.sh')}`, diff --git a/testing/fixtures/shellcheck/shell-directive.bash b/testing/fixtures/shellcheck/shell-directive.bash new file mode 100644 index 000000000..424fbf695 --- /dev/null +++ b/testing/fixtures/shellcheck/shell-directive.bash @@ -0,0 +1,12 @@ +#!/bin/env ksh +# this shebang must be overriden by shell directive + # this line must be ignored + + # shellcheck disable=SC1072 shell=sh enable=require-variable-braces + + +if [[ -n "$TERM" ]]; then + echo "$TERM" +fi + +# shellcheck shell=dash