11import { describe , it , expect } from "vitest" ;
22import { LanguageRegistry } from "../languages/language-registry.js" ;
3+ import { StrictLanguageConfigSchema } from "../languages/types.js" ;
34import { typescriptConfig } from "../languages/configs/typescript.js" ;
45import { pythonConfig } from "../languages/configs/python.js" ;
56
@@ -32,9 +33,9 @@ describe("LanguageRegistry", () => {
3233 expect ( registry . getForFile ( "file.unknown" ) ) . toBeNull ( ) ;
3334 } ) ;
3435
35- it ( "returns null for files without extensions" , ( ) => {
36+ it ( "returns null for files without extensions and no filename match " , ( ) => {
3637 const registry = new LanguageRegistry ( ) ;
37- expect ( registry . getForFile ( "Makefile " ) ) . toBeNull ( ) ;
38+ expect ( registry . getForFile ( "SOMEFILE " ) ) . toBeNull ( ) ;
3839 } ) ;
3940
4041 it ( "lists all registered languages" , ( ) => {
@@ -48,10 +49,10 @@ describe("LanguageRegistry", () => {
4849 } ) ;
4950
5051 describe ( "createDefault" , ( ) => {
51- it ( "registers all 12 built-in language configs" , ( ) => {
52+ it ( "registers all 38 built-in language configs" , ( ) => {
5253 const registry = LanguageRegistry . createDefault ( ) ;
5354 const all = registry . getAllLanguages ( ) ;
54- expect ( all . length ) . toBe ( 12 ) ;
55+ expect ( all . length ) . toBe ( 38 ) ;
5556 } ) ;
5657
5758 it ( "maps all expected extensions" , ( ) => {
@@ -88,4 +89,107 @@ describe("LanguageRegistry", () => {
8889 }
8990 } ) ;
9091 } ) ;
92+
93+ describe ( "Non-code language configs" , ( ) => {
94+ it ( "detects all non-code file types via extension" , ( ) => {
95+ const registry = LanguageRegistry . createDefault ( ) ;
96+ const expectations : [ string , string ] [ ] = [
97+ [ "README.md" , "markdown" ] ,
98+ [ "config.yaml" , "yaml" ] ,
99+ [ "package.json" , "json" ] ,
100+ [ "config.toml" , "toml" ] ,
101+ [ ".env" , "env" ] ,
102+ [ "pom.xml" , "xml" ] ,
103+ [ "Dockerfile" , "dockerfile" ] ,
104+ [ "schema.sql" , "sql" ] ,
105+ [ "schema.graphql" , "graphql" ] ,
106+ [ "types.proto" , "protobuf" ] ,
107+ [ "main.tf" , "terraform" ] ,
108+ [ "Makefile" , "makefile" ] ,
109+ [ "deploy.sh" , "shell" ] ,
110+ [ "index.html" , "html" ] ,
111+ [ "styles.css" , "css" ] ,
112+ [ "data.csv" , "csv" ] ,
113+ [ "deploy.ps1" , "powershell" ] ,
114+ ] ;
115+ for ( const [ file , expectedId ] of expectations ) {
116+ const config = registry . getForFile ( file ) ;
117+ expect ( config ?. id , `${ file } should be detected as ${ expectedId } ` ) . toBe ( expectedId ) ;
118+ }
119+ } ) ;
120+
121+ it ( "detects filename-based configs (Dockerfile, Makefile, Jenkinsfile)" , ( ) => {
122+ const registry = LanguageRegistry . createDefault ( ) ;
123+ expect ( registry . getForFile ( "Dockerfile" ) ?. id ) . toBe ( "dockerfile" ) ;
124+ expect ( registry . getForFile ( "Makefile" ) ?. id ) . toBe ( "makefile" ) ;
125+ expect ( registry . getForFile ( "Jenkinsfile" ) ?. id ) . toBe ( "jenkinsfile" ) ;
126+ expect ( registry . getForFile ( "src/Dockerfile" ) ?. id ) . toBe ( "dockerfile" ) ;
127+ expect ( registry . getForFile ( "build/Makefile" ) ?. id ) . toBe ( "makefile" ) ;
128+ } ) ;
129+
130+ it ( "detects filename-based configs for docker-compose" , ( ) => {
131+ const registry = LanguageRegistry . createDefault ( ) ;
132+ expect ( registry . getForFile ( "docker-compose.yml" ) ?. id ) . toBe ( "docker-compose" ) ;
133+ expect ( registry . getForFile ( "docker-compose.yaml" ) ?. id ) . toBe ( "docker-compose" ) ;
134+ expect ( registry . getForFile ( "compose.yml" ) ?. id ) . toBe ( "docker-compose" ) ;
135+ } ) ;
136+
137+ it ( "detects .env file variants" , ( ) => {
138+ const registry = LanguageRegistry . createDefault ( ) ;
139+ expect ( registry . getForFile ( ".env" ) ?. id ) . toBe ( "env" ) ;
140+ expect ( registry . getForFile ( ".env.local" ) ?. id ) . toBe ( "env" ) ;
141+ expect ( registry . getForFile ( ".env.production" ) ?. id ) . toBe ( "env" ) ;
142+ } ) ;
143+ } ) ;
144+
145+ describe ( "StrictLanguageConfigSchema refinement" , ( ) => {
146+ it ( "rejects configs with empty extensions AND no filenames" , ( ) => {
147+ const result = StrictLanguageConfigSchema . safeParse ( {
148+ id : "empty-lang" ,
149+ displayName : "Empty" ,
150+ extensions : [ ] ,
151+ concepts : [ "nothing" ] ,
152+ filePatterns : { entryPoints : [ ] , barrels : [ ] , tests : [ ] , config : [ ] } ,
153+ } ) ;
154+ expect ( result . success ) . toBe ( false ) ;
155+ if ( ! result . success ) {
156+ expect ( result . error . issues [ 0 ] . message ) . toContain ( "at least one extension or filename" ) ;
157+ }
158+ } ) ;
159+
160+ it ( "rejects configs with empty extensions AND empty filenames" , ( ) => {
161+ const result = StrictLanguageConfigSchema . safeParse ( {
162+ id : "empty-lang" ,
163+ displayName : "Empty" ,
164+ extensions : [ ] ,
165+ filenames : [ ] ,
166+ concepts : [ "nothing" ] ,
167+ filePatterns : { entryPoints : [ ] , barrels : [ ] , tests : [ ] , config : [ ] } ,
168+ } ) ;
169+ expect ( result . success ) . toBe ( false ) ;
170+ } ) ;
171+
172+ it ( "accepts configs with extensions but no filenames" , ( ) => {
173+ const result = StrictLanguageConfigSchema . safeParse ( {
174+ id : "ext-lang" ,
175+ displayName : "ExtLang" ,
176+ extensions : [ ".ext" ] ,
177+ concepts : [ "something" ] ,
178+ filePatterns : { entryPoints : [ ] , barrels : [ ] , tests : [ ] , config : [ ] } ,
179+ } ) ;
180+ expect ( result . success ) . toBe ( true ) ;
181+ } ) ;
182+
183+ it ( "accepts configs with filenames but empty extensions" , ( ) => {
184+ const result = StrictLanguageConfigSchema . safeParse ( {
185+ id : "filename-lang" ,
186+ displayName : "FilenameLang" ,
187+ extensions : [ ] ,
188+ filenames : [ "Specialfile" ] ,
189+ concepts : [ "something" ] ,
190+ filePatterns : { entryPoints : [ ] , barrels : [ ] , tests : [ ] , config : [ ] } ,
191+ } ) ;
192+ expect ( result . success ) . toBe ( true ) ;
193+ } ) ;
194+ } ) ;
91195} ) ;
0 commit comments