vbnet.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import type { Node as SyntaxNode } from 'web-tree-sitter';
  2. import { getNodeText } from '../tree-sitter-helpers';
  3. import type { LanguageExtractor } from '../tree-sitter-types';
  4. /**
  5. * The vendored VB.NET grammar has no true end-of-file token (its `_eof` rule is
  6. * a literal-`$` placeholder that never matches real input), so a file whose
  7. * last line lacks a trailing newline ends every parse with a MISSING-newline
  8. * error on the final statement. Appending a newline is offset-preserving for
  9. * all existing content.
  10. */
  11. export function ensureTrailingNewline(source: string): string {
  12. return source.endsWith('\n') ? source : source + '\n';
  13. }
  14. /** Case-insensitive member-modifier scan (VB keywords are case-insensitive). */
  15. function hasModifier(node: SyntaxNode, re: RegExp): boolean {
  16. for (let i = 0; i < node.childCount; i++) {
  17. const child = node.child(i);
  18. if (child?.type === 'member_modifier' && re.test(child.text)) return true;
  19. }
  20. return false;
  21. }
  22. /**
  23. * A VB.NET method's declared return type (`Function Foo(...) As Bar`),
  24. * normalized to the bare class name a chained `Foo.Create().Bar()` could be
  25. * called on (the #645/#608 mechanism). The type lives in the method's
  26. * `as_clause` child; predefined types (Integer/String/…) and arrays yield
  27. * undefined, generics `List(Of Foo)` unwrap to the base type, and a dotted
  28. * `Ns.Foo` reduces to the simple name. Subs have no as_clause → undefined.
  29. */
  30. function extractVbnetReturnType(node: SyntaxNode, source: string): string | undefined {
  31. const asClause = node.namedChildren.find((c: SyntaxNode) => c.type === 'as_clause');
  32. if (!asClause) return undefined;
  33. const typeNode = asClause.childForFieldName('declared_type');
  34. if (!typeNode || typeNode.type === 'predefined_type' || typeNode.type === 'array_type') return undefined;
  35. let t = getNodeText(typeNode, source).trim();
  36. t = t.replace(/\?+$/, ''); // nullable `Foo?`
  37. t = t.replace(/\(\s*Of\b[^)]*\)/gi, ''); // generics `List(Of Foo)` → `List`
  38. const last = t.split('.').pop()?.trim();
  39. if (!last || !/^[A-Za-z_]\w*$/.test(last)) return undefined;
  40. return last;
  41. }
  42. export const vbnetExtractor: LanguageExtractor = {
  43. preParse: ensureTrailingNewline,
  44. functionTypes: [],
  45. // VB Modules are static containers (Shared members, no instantiation) —
  46. // indexed as classes so their members get normal containment/qualification.
  47. classTypes: ['class_declaration', 'module_declaration'],
  48. methodTypes: [
  49. 'method_declaration',
  50. 'constructor_declaration',
  51. // `Declare Function GetWindowLong Lib "user32" ...` (P/Invoke)
  52. 'external_method_declaration',
  53. // Interface members are distinct node types in this grammar (unlike C#).
  54. 'interface_method_declaration',
  55. // `MustOverride Sub/Function ...` — body-less abstract members.
  56. 'abstract_method_declaration',
  57. ],
  58. interfaceTypes: ['interface_declaration'],
  59. structTypes: ['structure_declaration'],
  60. enumTypes: ['enum_declaration'],
  61. enumMemberTypes: ['enum_member_declaration'],
  62. typeAliasTypes: ['delegate_declaration'],
  63. packageTypes: ['namespace_declaration'],
  64. extractPackage: (node: SyntaxNode, source: string) => {
  65. const name = node.childForFieldName('name');
  66. return name ? getNodeText(name, source) : null;
  67. },
  68. importTypes: ['imports_statement'],
  69. // VB uses parentheses for BOTH calls and indexing, so the grammar can only
  70. // split them heuristically (empty parens → invocation, args → array access;
  71. // even Roslyn parses both as InvocationExpression and disambiguates during
  72. // binding). Both are treated as call sites — extractCall has a vbnet branch
  73. // — and name matching simply never resolves an index read on a collection.
  74. callTypes: ['invocation_expression', 'array_access_expression', 'generic_invocation_expression'],
  75. variableTypes: ['declaration_statement'],
  76. fieldTypes: ['field_declaration'],
  77. propertyTypes: ['property_declaration', 'interface_property_declaration', 'abstract_property_declaration'],
  78. nameField: 'name',
  79. bodyField: 'body',
  80. paramsField: 'parameters',
  81. // Method/property statements are direct children of the declaration node
  82. // (this grammar has no body wrapper), so the node is its own body — without
  83. // this, calls inside every Sub/Function would be skipped.
  84. resolveBody: (node: SyntaxNode) => node,
  85. getReturnType: extractVbnetReturnType,
  86. getVisibility: (node) => {
  87. if (hasModifier(node, /^private$/i)) return 'private';
  88. if (hasModifier(node, /^protected(\s+friend)?$/i)) return 'protected';
  89. if (hasModifier(node, /^friend$/i)) return 'internal';
  90. return 'public'; // VB members default to Public in practice
  91. },
  92. isStatic: (node) => hasModifier(node, /^shared$/i),
  93. isConst: (node) => hasModifier(node, /^const$/i) || (hasModifier(node, /^shared$/i) && hasModifier(node, /^readonly$/i)),
  94. isAsync: (node) => hasModifier(node, /^async$/i),
  95. extractImport: (node, source) => {
  96. const importText = source.substring(node.startIndex, node.endIndex).trim();
  97. // `Imports System.Collections.Generic` / `Imports Alias = Some.Namespace` /
  98. // `Imports Global.Company.Product`. The name reference is the last
  99. // qualified/simple/global name child (skips the alias identifier).
  100. const nameNode = [...node.namedChildren]
  101. .reverse()
  102. .find((c: SyntaxNode) =>
  103. c.type === 'qualified_name' || c.type === 'simple_name' || c.type === 'global_qualified_name' || c.type === 'identifier'
  104. );
  105. if (nameNode) {
  106. return { moduleName: getNodeText(nameNode, source), signature: importText };
  107. }
  108. return null;
  109. },
  110. visitNode: (node, ctx) => {
  111. // Events are indexed so `RaiseEvent X` / `Handles obj.X` flows have a
  112. // findable declaration (WinForms/WPF code is built around them).
  113. if (node.type === 'event_declaration' || node.type === 'custom_event_declaration') {
  114. const nameNode = node.childForFieldName('name');
  115. if (nameNode) {
  116. ctx.createNode('field', getNodeText(nameNode, ctx.source), node);
  117. }
  118. return true;
  119. }
  120. // `Sub New(...)` lexes as one token with no name field — without this,
  121. // constructors index as `<anonymous>`.
  122. if (node.type === 'constructor_declaration') {
  123. const ctor = ctx.createNode('method', 'New', node);
  124. if (ctor) {
  125. ctx.pushScope(ctor.id);
  126. ctx.visitFunctionBody(node, ctor.id);
  127. ctx.popScope();
  128. }
  129. return true;
  130. }
  131. return false;
  132. },
  133. };