bashLanguage top-level constant

Grammar const bashLanguage

Bash and POSIX shell scripts.

Implementation

const bashLanguage = Grammar(
  name: 'bash',
  aliases: ['sh', 'shell', 'zsh', 'console'],
  fileExtensions: ['sh', 'bash', 'zsh'],
  shebangs: ['bash', 'sh', 'zsh', 'dash', 'ksh'],
  signatures: [
    r'^\s*(?:if|while|until)\s+\[\[?\s',
    r'^\s*(?:fi|done|esac)\s*$',
    r'\$\{?[A-Za-z_]\w*\}?',
    r'^\s*(?:export|local|readonly)\s+[A-Za-z_]\w*=',
    r'^\s*(?:echo|cd|mkdir|rm|sudo|apt|brew|npm|git)\s',
  ],
  rules: [
    TokenRule(r'^#!.*$', scope: Scopes.meta),
    TokenRule(r'(?<=^|[\s;|&(])#.*$', scope: Scopes.comment),
    IncludeRule('heredocs'),
    // A backslash outside quotes escapes the next character, so `\'` is not
    // the start of a string.
    TokenRule(r'\\[\s\S]'),
    IncludeRule('strings'),
    IncludeRule('variables'),
    // `NAME=value` and `NAME+=value`.
    TokenRule(
      r'(?<=^|[\s;|&(])[A-Za-z_]\w*(?=\+?=(?!=))',
      scope: Scopes.variable,
    ),
    TokenRule(r'(?<=^|[\s;|&(])--?[A-Za-z][\w-]*', scope: Scopes.attribute),
    TokenRule(r'(?<![\w.-])\d+(?![\w.])', scope: Scopes.number),
    KeywordRule(
      {
        Scopes.keyword: [
          'case', 'coproc', 'do', 'done', 'elif', 'else', 'esac', 'fi', //
          'for', 'function', 'if', 'in', 'select', 'then', 'time', 'until',
          'while',
        ],
        Scopes.function: [
          'alias', 'bg', 'break', 'builtin', 'cd', 'command', 'continue', //
          'declare', 'echo', 'eval', 'exec', 'exit', 'export', 'fg',
          'getopts', 'hash', 'jobs', 'kill', 'let', 'local', 'printf', 'pwd',
          'read', 'readonly', 'return', 'set', 'shift', 'source', 'test',
          'trap', 'type', 'typeset', 'ulimit', 'umask', 'unalias', 'unset',
          'wait',
        ],
        Scopes.literal: ['true', 'false'],
      },
      word: r'(?<![\w$/.-])[A-Za-z_][\w-]*',
      otherwise: [
        // `name() {` and `function name` declare functions.
        TokenRule(
          r'[A-Za-z_][\w-]*(?=[ \t]*\([ \t]*\))',
          scope: Scopes.function,
        ),
      ],
    ),
    TokenRule(r'&&|\|\||;;|[|&;]|[<>]{1,2}&?', scope: Scopes.operator),
  ],
  repository: {
    // `<<EOF … EOF`. The rest of the opening line stays ordinary text; the
    // body starts on the next line. A quoted delimiter disables expansion.
    'heredocs': [
      RegionRule(
        begin: r'''(?<!<)(<<-?)[ \t]*(['"])([A-Za-z_][\w-]*)\2([^\n]*)''',
        end: r'^\t*\3$',
        beginCaptures: {1: Scopes.operator, 3: Scopes.meta},
        endScope: Scopes.meta,
        endReferencesBegin: true,
        rules: [TokenRule(r'[^\n]+', scope: Scopes.string)],
      ),
      RegionRule(
        begin: r'(?<!<)(<<-?)[ \t]*\\?([A-Za-z_][\w-]*)([^\n]*)',
        end: r'^\t*\2$',
        beginCaptures: {1: Scopes.operator, 2: Scopes.meta},
        endScope: Scopes.meta,
        endReferencesBegin: true,
        rules: [
          TokenRule(r'[^$`\\\n]+', scope: Scopes.string),
          TokenRule(r'\\[$`\\\n]', scope: Scopes.stringEscape),
          IncludeRule('variables'),
        ],
      ),
    ],
    'strings': [
      RegionRule(
        begin: r"\$'",
        end: "'",
        scope: Scopes.string,
        rules: [TokenRule(r'\\.', scope: Scopes.stringEscape)],
      ),
      RegionRule(begin: "'", end: "'", scope: Scopes.string),
      RegionRule(
        begin: '"',
        end: '"',
        scope: Scopes.string,
        rules: [
          TokenRule(r'\\[\\"$`\n]', scope: Scopes.stringEscape),
          IncludeRule('variables'),
        ],
      ),
      RegionRule(
        begin: '`',
        end: '`',
        scope: Scopes.interpolation,
        rules: [IncludeRule.self],
      ),
    ],
    'variables': [
      RegionRule(
        begin: r'\$\(\(',
        end: r'\)\)',
        scope: Scopes.interpolation,
        rules: [IncludeRule.self],
      ),
      RegionRule(
        begin: r'\$\(',
        end: r'\)',
        scope: Scopes.interpolation,
        rules: [IncludeRule('parens'), IncludeRule.self],
      ),
      RegionRule(begin: r'\$\{', end: r'\}', scope: Scopes.variable),
      TokenRule(r'\$(?:[A-Za-z_]\w*|[0-9@*#?$!-])', scope: Scopes.variable),
    ],
    'parens': [
      RegionRule(
        begin: r'\(',
        end: r'\)',
        rules: [IncludeRule('parens'), IncludeRule.self],
      ),
    ],
  },
);