feat(bash): add Bash language provider and all extractors

Full tree-sitter-based Bash support using tree-sitter-bash@0.23.3
(N-API binding, ABI version 14):
- BASH_QUERIES: function definitions, source/dot imports, command calls,
  variable assignments
- Minimal extractors (no classes, no types, no heritage — shell semantics)
- Export checker (all public, like Ruby)
- Import resolver for source/dot file paths
- Registered in all exhaustive records
This commit is contained in:
Christian C. Berclaz 2026-05-01 23:15:50 +02:00
parent c1f8cc51a1
commit c350181c27
No known key found for this signature in database
14 changed files with 296 additions and 0 deletions

View file

@ -0,0 +1,6 @@
import { SupportedLanguages } from 'gitnexus-shared';
import type { CallExtractionConfig } from '../../call-types.js';
export const bashCallConfig: CallExtractionConfig = {
language: SupportedLanguages.Bash,
};

View file

@ -0,0 +1,8 @@
import { SupportedLanguages } from 'gitnexus-shared';
import type { ClassExtractionConfig } from '../../class-types.js';
export const bashClassConfig: ClassExtractionConfig = {
language: SupportedLanguages.Bash,
typeDeclarationNodes: [],
fileScopeNodeTypes: ['program'],
};

View file

@ -246,3 +246,6 @@ export const rubyExportChecker: ExportChecker = (_node, _name) => true;
/** Dart: public if no leading underscore (convention, same as Python). */
export const dartExportChecker: ExportChecker = (_node, name) => !name.startsWith('_');
/** Bash: all definitions are public (no visibility modifiers in shell). */
export const bashExportChecker: ExportChecker = (_node, _name) => true;

View file

@ -0,0 +1,26 @@
import { SupportedLanguages } from 'gitnexus-shared';
import type { FieldExtractionConfig } from '../generic.js';
export const bashFieldConfig: FieldExtractionConfig = {
language: SupportedLanguages.Bash,
typeDeclarationNodes: [],
fieldNodeTypes: [],
bodyNodeTypes: [],
defaultVisibility: 'public',
extractName(_node) {
return undefined;
},
extractType(_node) {
return undefined;
},
extractVisibility(_node) {
return 'public';
},
isStatic(_node) {
return false;
},
isReadonly(_node) {
return false;
},
};

View file

@ -0,0 +1,6 @@
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
export const bashHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.Bash,
};

View file

@ -0,0 +1,21 @@
/**
* Bash import resolution config.
* Bash uses `source file.sh` or `. file.sh` to include other scripts.
*/
import { SupportedLanguages } from 'gitnexus-shared';
import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js';
import { suffixResolve } from '../utils.js';
export const bashSourceStrategy: ImportResolverStrategy = (rawImportPath, _filePath, ctx) => {
const cleanPath = rawImportPath.replace(/^['"]|['"]$/g, '').replace(/^\$\{.*\}\/?/, '');
if (!cleanPath) return null;
const pathParts = cleanPath.split('/').filter(Boolean);
const resolved = suffixResolve(pathParts, ctx.normalizedFileList, ctx.allFileList, ctx.index);
return resolved ? { kind: 'files', files: [resolved] } : null;
};
export const bashImportConfig: ImportResolutionConfig = {
language: SupportedLanguages.Bash,
strategies: [bashSourceStrategy],
};

View file

@ -47,6 +47,9 @@ export const EXTENSIONS = [
'.swift',
// Ruby
'.rb',
// Bash
'.sh',
'.bash',
];
/**

View file

@ -0,0 +1,108 @@
/**
* Bash Language Provider
*
* Key Bash traits:
* - importSemantics: 'wildcard-leaf' (source brings everything into scope)
* - No classes or OO constructs
* - Functions defined with `function foo() {}` or `foo() {}`
* - Variables are untyped (all strings)
* - Imports via `source file.sh` or `. file.sh`
*/
import { SupportedLanguages } from 'gitnexus-shared';
import { createClassExtractor } from '../class-extractors/generic.js';
import { bashClassConfig } from '../class-extractors/configs/bash.js';
import { defineLanguage } from '../language-provider.js';
import { typeConfig as bashConfig } from '../type-extractors/bash.js';
import { bashExportChecker } from '../export-detection.js';
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
import { bashImportConfig } from '../import-resolvers/configs/bash.js';
import { BASH_QUERIES } from '../tree-sitter-queries.js';
import { createFieldExtractor } from '../field-extractors/generic.js';
import { bashFieldConfig } from '../field-extractors/configs/bash.js';
import { createMethodExtractor } from '../method-extractors/generic.js';
import { bashMethodConfig } from '../method-extractors/configs/bash.js';
import { createVariableExtractor } from '../variable-extractors/generic.js';
import { bashVariableConfig } from '../variable-extractors/configs/bash.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { bashCallConfig } from '../call-extractors/configs/bash.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { bashHeritageConfig } from '../heritage-extractors/configs/bash.js';
const BUILT_INS: ReadonlySet<string> = new Set([
'echo',
'printf',
'read',
'cd',
'pwd',
'ls',
'cat',
'grep',
'sed',
'awk',
'find',
'xargs',
'sort',
'uniq',
'wc',
'head',
'tail',
'cut',
'tr',
'tee',
'test',
'true',
'false',
'exit',
'return',
'export',
'unset',
'local',
'declare',
'readonly',
'shift',
'set',
'eval',
'exec',
'trap',
'wait',
'kill',
'sleep',
'date',
'basename',
'dirname',
'mktemp',
'mkdir',
'rm',
'cp',
'mv',
'chmod',
'chown',
'touch',
'ln',
'which',
'command',
'type',
'hash',
'getopts',
'pushd',
'popd',
'dirs',
]);
export const bashProvider = defineLanguage({
id: SupportedLanguages.Bash,
extensions: ['.sh', '.bash'],
treeSitterQueries: BASH_QUERIES,
typeConfig: bashConfig,
exportChecker: bashExportChecker,
importResolver: createImportResolver(bashImportConfig),
importSemantics: 'wildcard-leaf',
callExtractor: createCallExtractor(bashCallConfig),
fieldExtractor: createFieldExtractor(bashFieldConfig),
methodExtractor: createMethodExtractor(bashMethodConfig),
variableExtractor: createVariableExtractor(bashVariableConfig),
classExtractor: createClassExtractor(bashClassConfig),
heritageExtractor: createHeritageExtractor(bashHeritageConfig),
builtInNames: BUILT_INS,
});

View file

@ -24,6 +24,7 @@ import { rubyProvider } from './ruby.js';
import { swiftProvider } from './swift.js';
import { dartProvider } from './dart.js';
import { vueProvider } from './vue.js';
import { bashProvider } from './bash.js';
import { cobolProvider } from './cobol.js';
export const providers = {
@ -41,6 +42,7 @@ export const providers = {
[SupportedLanguages.Ruby]: rubyProvider,
[SupportedLanguages.Swift]: swiftProvider,
[SupportedLanguages.Dart]: dartProvider,
[SupportedLanguages.Bash]: bashProvider,
[SupportedLanguages.Vue]: vueProvider,
[SupportedLanguages.Cobol]: cobolProvider,
} satisfies Record<SupportedLanguages, LanguageProvider>;

View file

@ -0,0 +1,38 @@
import { SupportedLanguages } from 'gitnexus-shared';
import type { MethodExtractionConfig, ParameterInfo } from '../../method-types.js';
import type { SyntaxNode } from '../../utils/ast-helpers.js';
export const bashMethodConfig: MethodExtractionConfig = {
language: SupportedLanguages.Bash,
typeDeclarationNodes: [],
methodNodeTypes: ['function_definition'],
bodyNodeTypes: [],
extractName(node: SyntaxNode) {
return node.childForFieldName('name')?.text;
},
extractReturnType(_node) {
return undefined;
},
extractParameters(_node): ParameterInfo[] {
return [];
},
extractVisibility(_node) {
return 'public';
},
isStatic(_node) {
return false;
},
isAbstract(_node) {
return false;
},
isFinal(_node) {
return false;
},
};

View file

@ -1373,6 +1373,23 @@ export const DART_QUERIES = `
(type_identifier) @heritage.trait))) @heritage
`;
export const BASH_QUERIES = `
; Functions: function foo() {} or foo() {}
(function_definition name: (word) @name) @definition.function
; Source imports: source file.sh or . file.sh
(command
name: (command_name) @_cmd
argument: (_) @import.source
(#match? @_cmd "^(source|\\\\.)$")) @import
; Command calls: foo args...
(command name: (command_name (word) @call.name)) @call
; Variable assignments: FOO=bar
(variable_assignment name: (variable_name) @name) @definition.variable
`;
import { SupportedLanguages } from 'gitnexus-shared';
export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
@ -1390,6 +1407,7 @@ export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
[SupportedLanguages.Ruby]: RUBY_QUERIES,
[SupportedLanguages.Swift]: SWIFT_QUERIES,
[SupportedLanguages.Dart]: DART_QUERIES,
[SupportedLanguages.Bash]: BASH_QUERIES,
[SupportedLanguages.Vue]: TYPESCRIPT_QUERIES, // Vue <script> blocks are parsed as TypeScript
[SupportedLanguages.Cobol]: '', // Standalone regex processor — no tree-sitter queries
};

View file

@ -0,0 +1,18 @@
import type { SyntaxNode } from '../utils/ast-helpers.js';
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor } from './types.js';
const extractDeclaration: TypeBindingExtractor = (
_node: SyntaxNode,
_env: Map<string, string>,
): void => {};
const extractParameter: ParameterExtractor = (
_node: SyntaxNode,
_env: Map<string, string>,
): void => {};
export const typeConfig: LanguageTypeConfig = {
declarationNodeTypes: new Set(['variable_assignment']),
extractDeclaration,
extractParameter,
};

View file

@ -0,0 +1,34 @@
import { SupportedLanguages } from 'gitnexus-shared';
import type { VariableExtractionConfig, VariableVisibility } from '../../variable-types.js';
import type { SyntaxNode } from '../../utils/ast-helpers.js';
export const bashVariableConfig: VariableExtractionConfig = {
language: SupportedLanguages.Bash,
constNodeTypes: [],
staticNodeTypes: [],
variableNodeTypes: ['variable_assignment'],
extractName(node: SyntaxNode) {
return node.childForFieldName('name')?.text;
},
extractType(_node) {
return undefined;
},
extractVisibility(_node): VariableVisibility {
return 'public';
},
isConst(_node) {
return false;
},
isStatic(_node) {
return false;
},
isMutable(_node) {
return true;
},
};

View file

@ -153,6 +153,11 @@ const SOURCES: Record<string, GrammarSource> = {
'Likely cause: native compile failed at install (missing python3/make/g++). ' +
`See ${ISSUES_URL}/1125.`,
},
[SupportedLanguages.Bash]: {
load: () => _require('tree-sitter-bash'),
unavailableNote:
'Bash parsing requires `tree-sitter-bash`. Check the install and native binding.',
},
[SupportedLanguages.Kotlin]: {
load: () => _require('tree-sitter-kotlin'),
optional: true,