rubyLanguage top-level constant

Grammar const rubyLanguage

Ruby, including Gemfiles, Podfiles, Rakefiles and Fastlane files.

Implementation

const rubyLanguage = Grammar(
  name: 'ruby',
  aliases: ['rb', 'jruby', 'podfile', 'gemfile'],
  fileExtensions: ['rb', 'rake', 'gemspec', 'podspec', 'ru', 'rbw', 'thor'],
  fileNames: [
    'Gemfile', 'Rakefile', 'Podfile', 'Fastfile', 'Brewfile', 'Appfile', //
    'Matchfile', 'Pluginfile', 'Vagrantfile', 'Guardfile', 'Dangerfile',
    'Berksfile', 'Capfile',
  ],
  shebangs: ['ruby', 'jruby', 'rake'],
  signatures: [
    r'''^\s*require(?:_relative)?\s+['"][\w./-]+['"]\s*$''',
    r'\bdo\s*\|[\w, *&]+\|',
    r'^\s*def\s+(?:self\.)?\w+[?!]?(?:\(.*\))?\s*$',
    r'^\s*(?:attr_accessor|attr_reader|puts)\s',
    r'^\s*(?:module|class)\s+[A-Z]\w*(?:::\w+)*(?:\s*<\s*[A-Z][\w:]*)?\s*$',
  ],
  rules: [
    TokenRule(r'^#!.*$', scope: Scopes.meta),
    // `=begin` … `=end` block comments.
    RegionRule(
      begin: r'^=begin\b',
      end: r'^=end\b.*$|(?![\s\S])',
      scope: Scopes.comment,
    ),
    // Everything after `__END__` is data.
    RegionRule(begin: r'^__END__$', end: r'(?![\s\S])', scope: Scopes.comment),
    IncludeRule('heredocs'),
    LineComment('#'),
    // Operator methods: def `(cmd), def [](i), def <=>(o), def -@.
    TokenRule(
      r'(?<=\bdef\s)(?:`|\[\]=?|[-+*/%<>=!~^&|]+@?)',
      scope: Scopes.function,
    ),
    // Character literals: ?a, ?", ?\n; not the ternary `a ? b : c`.
    TokenRule(
      r'(?<![\w)\]}])\?(?:\\(?:[MC]-)?[\s\S]|[^\s\\])(?!\w)',
      scope: Scopes.string,
    ),
    IncludeRule('strings'),
    // Symbols: `:name`, `:"quoted"`, `:name?`, and `key:` hash keys.
    TokenRule(r'(?<![:\w]):"(?:[^"\\\n]|\\.)*"', scope: _symbol),
    TokenRule(r'(?<![:\w]):[A-Za-z_]\w*[?!]?(?!:)', scope: _symbol),
    TokenRule(r'(?<![:\w])[A-Za-z_]\w*[?!]?:(?=[ \t\n)])', scope: _symbol),
    // A `/` starts a regular expression only where an expression starts.
    // It may span lines (`/x` mode) and interpolate.
    RegionRule(
      begin:
          r'(?<=(?:^|[(,=~!|&{\[;>])[ \t]*)/'
          r'|(?<=\b(?:if|unless|when|and|or|not|return|puts)[ \t]+)/(?![ \t])',
      end: r'/[imxounse]*',
      scope: Scopes.regexp,
      rules: [
        TokenRule(r'\\[\s\S]', scope: Scopes.stringEscape),
        Interpolation('#{', '}'),
        TokenRule(r'\[(?:[^\]\\\n]|\\.)*\]'),
      ],
    ),
    TokenRule(r'@@?[A-Za-z_]\w*', scope: Scopes.variable),
    TokenRule(
      r'''\$(?:[A-Za-z_]\w*|[0-9]+|[!@&`'+~=/\\,;.<>*$?:"_])''',
      scope: Scopes.variable,
    ),
    Numbers(suffix: '[ri]{0,2}'),
    // After a `.`, a name is a method: never a keyword.
    TokenRule(r'(?<=\.)[A-Za-z_]\w*[?!]?(?=[ \t]*\()', scope: Scopes.function),
    TokenRule(r'(?<=\.)[A-Za-z_]\w*[?!]?'),
    KeywordRule(
      {
        Scopes.keyword: [
          'BEGIN', 'END', 'alias', 'and', 'begin', 'break', 'case', //
          'class', 'def', 'defined?', 'do', 'else', 'elsif', 'end', 'ensure',
          'for', 'if', 'in', 'module', 'next', 'not', 'or', 'redo', 'rescue',
          'retry', 'return', 'then', 'undef', 'unless', 'until', 'when',
          'while', 'yield', '__method__', '__FILE__', '__LINE__', '__dir__',
          '__ENCODING__',
        ],
        Scopes.literal: ['true', 'false', 'nil'],
        Scopes.variableLanguage: ['self', 'super'],
        Scopes.function: [
          'attr_accessor', 'attr_reader', 'attr_writer', 'block_given?', //
          'catch', 'extend', 'format', 'freeze', 'gets', 'include',
          'lambda', 'loop', 'module_function', 'p', 'pp', 'prepend',
          'print', 'private', 'private_constant', 'proc', 'protected',
          'public', 'puts', 'raise', 'rand', 'require', 'require_relative',
          'sleep', 'sprintf', 'throw',
        ],
      },
      word: r'(?<![\w@$])[A-Za-z_]\w*(?:[?!](?!=))?',
      otherwise: [
        TokenRule(r'(?<=\bdef\s)[A-Za-z_]\w*[?!=]?', scope: Scopes.function),
        CommonRules.capitalizedType,
        TokenRule(r'[A-Za-z_]\w*[?!]?(?=\()', scope: Scopes.function),
      ],
    ),
    // `&:name` passes a symbol as a block.
    TokenRule(r'&(?=:[A-Za-z_])', scope: Scopes.operator),
    Operators('+-*/%=<>!&|^~?:'),
  ],
  repository: {
    // `<<~EOS`, `<<-EOS`, `<<EOS`, with optional quotes. The body starts on
    // the next line; single quotes disable interpolation.
    'heredocs': [
      RegionRule(
        begin: r"(<<[~-]?)'([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)],
      ),
      RegionRule(
        begin: r'(<<[~-])"?([A-Za-z_]\w*)"?([^\n]*)',
        end: r'^[ \t]*\2$',
        beginCaptures: {1: Scopes.operator, 2: Scopes.meta},
        endScope: Scopes.meta,
        endReferencesBegin: true,
        rules: [IncludeRule('heredocBody')],
      ),
      RegionRule(
        // Only after a space, `(`, `,` or `=`, so `a<<b` stays a shift;
        // `class <<self` is not a heredoc.
        begin: r'(?<=[\s(,=])(<<)(?!self\b)"?([A-Za-z_]\w*)"?([^\n]*)',
        end: r'^\2$',
        beginCaptures: {1: Scopes.operator, 2: Scopes.meta},
        endScope: Scopes.meta,
        endReferencesBegin: true,
        rules: [IncludeRule('heredocBody')],
      ),
    ],
    'heredocBody': [
      TokenRule(r'\\[\s\S]', scope: Scopes.stringEscape),
      Interpolation('#{', '}'),
      TokenRule(r'[^#\\\n]+|#', scope: Scopes.string),
    ],
    'strings': [
      QuotedString('"', multiline: true, rules: [Interpolation('#{', '}')]),
      QuotedString(
        "'",
        escape: null,
        multiline: true,
        rules: [TokenRule(r"\\[\\']", scope: Scopes.stringEscape)],
      ),
      QuotedString('`', multiline: true, rules: [Interpolation('#{', '}')]),
      // `%w[]`, `%i()`, `%q{}`, `%Q<>`, `%()`, `%r{}` and friends.
      _PercentLiteral('[qwis]', Scopes.string, interpolate: false),
      _PercentLiteral('[QWIx]?', Scopes.string, interpolate: true),
      _PercentLiteral('r', Scopes.regexp, interpolate: true, flags: '[imxo]*'),
    ],
  },
);