diff --git a/gitnexus/src/core/ingestion/call-extractors/configs/bash.ts b/gitnexus/src/core/ingestion/call-extractors/configs/bash.ts new file mode 100644 index 000000000..d1ede7d15 --- /dev/null +++ b/gitnexus/src/core/ingestion/call-extractors/configs/bash.ts @@ -0,0 +1,6 @@ +import { SupportedLanguages } from 'gitnexus-shared'; +import type { CallExtractionConfig } from '../../call-types.js'; + +export const bashCallConfig: CallExtractionConfig = { + language: SupportedLanguages.Bash, +}; diff --git a/gitnexus/src/core/ingestion/class-extractors/configs/bash.ts b/gitnexus/src/core/ingestion/class-extractors/configs/bash.ts new file mode 100644 index 000000000..d8cdc28b4 --- /dev/null +++ b/gitnexus/src/core/ingestion/class-extractors/configs/bash.ts @@ -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'], +}; diff --git a/gitnexus/src/core/ingestion/export-detection.ts b/gitnexus/src/core/ingestion/export-detection.ts index 3eb1b4f07..653ca4a83 100644 --- a/gitnexus/src/core/ingestion/export-detection.ts +++ b/gitnexus/src/core/ingestion/export-detection.ts @@ -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; diff --git a/gitnexus/src/core/ingestion/field-extractors/configs/bash.ts b/gitnexus/src/core/ingestion/field-extractors/configs/bash.ts new file mode 100644 index 000000000..12b14fb88 --- /dev/null +++ b/gitnexus/src/core/ingestion/field-extractors/configs/bash.ts @@ -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; + }, +}; diff --git a/gitnexus/src/core/ingestion/heritage-extractors/configs/bash.ts b/gitnexus/src/core/ingestion/heritage-extractors/configs/bash.ts new file mode 100644 index 000000000..0c1826488 --- /dev/null +++ b/gitnexus/src/core/ingestion/heritage-extractors/configs/bash.ts @@ -0,0 +1,6 @@ +import { SupportedLanguages } from 'gitnexus-shared'; +import type { HeritageExtractionConfig } from '../../heritage-types.js'; + +export const bashHeritageConfig: HeritageExtractionConfig = { + language: SupportedLanguages.Bash, +}; diff --git a/gitnexus/src/core/ingestion/import-resolvers/configs/bash.ts b/gitnexus/src/core/ingestion/import-resolvers/configs/bash.ts new file mode 100644 index 000000000..b46112bc2 --- /dev/null +++ b/gitnexus/src/core/ingestion/import-resolvers/configs/bash.ts @@ -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], +}; diff --git a/gitnexus/src/core/ingestion/import-resolvers/utils.ts b/gitnexus/src/core/ingestion/import-resolvers/utils.ts index 8d915eb7f..a2d595f3a 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/utils.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/utils.ts @@ -47,6 +47,9 @@ export const EXTENSIONS = [ '.swift', // Ruby '.rb', + // Bash + '.sh', + '.bash', ]; /** diff --git a/gitnexus/src/core/ingestion/languages/bash.ts b/gitnexus/src/core/ingestion/languages/bash.ts new file mode 100644 index 000000000..25fde734d --- /dev/null +++ b/gitnexus/src/core/ingestion/languages/bash.ts @@ -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 = 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, +}); diff --git a/gitnexus/src/core/ingestion/languages/index.ts b/gitnexus/src/core/ingestion/languages/index.ts index 041af775d..00b0d5536 100644 --- a/gitnexus/src/core/ingestion/languages/index.ts +++ b/gitnexus/src/core/ingestion/languages/index.ts @@ -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; diff --git a/gitnexus/src/core/ingestion/method-extractors/configs/bash.ts b/gitnexus/src/core/ingestion/method-extractors/configs/bash.ts new file mode 100644 index 000000000..4af60e1a6 --- /dev/null +++ b/gitnexus/src/core/ingestion/method-extractors/configs/bash.ts @@ -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; + }, +}; diff --git a/gitnexus/src/core/ingestion/tree-sitter-queries.ts b/gitnexus/src/core/ingestion/tree-sitter-queries.ts index 61030ff66..70574ca7b 100644 --- a/gitnexus/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus/src/core/ingestion/tree-sitter-queries.ts @@ -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 = { @@ -1390,6 +1407,7 @@ export const LANGUAGE_QUERIES: Record = { [SupportedLanguages.Ruby]: RUBY_QUERIES, [SupportedLanguages.Swift]: SWIFT_QUERIES, [SupportedLanguages.Dart]: DART_QUERIES, + [SupportedLanguages.Bash]: BASH_QUERIES, [SupportedLanguages.Vue]: TYPESCRIPT_QUERIES, // Vue