1- import { ConfigDescription , ConfigValueExtractor , ValueValidator } from '@salesforce/code-analyzer-engine-api' ;
1+ import {
2+ ConfigDescription ,
3+ ConfigValueExtractor ,
4+ ValueValidator
5+ } from '@salesforce/code-analyzer-engine-api' ;
26import { getMessage } from "./messages" ;
37import path from "node:path" ;
48import { makeUnique } from "./utils" ;
@@ -29,27 +33,33 @@ export type ESLintEngineConfig = {
2933 // Default: false
3034 disable_typescript_base_config : boolean
3135
32- // Extensions of the javascript files in your workspace that will be used to discover rules.
33- // Default: ['.js', '.cjs', '.mjs']
34- javascript_file_extensions : string [ ]
35-
36- // Extensions of the typescript files in your workspace that will be used to discover rules.
37- // Default: ['.ts']
38- typescript_file_extensions : string [ ]
36+ // Extensions of the files in your workspace that will be used to discover rules for javascript and typescript.
37+ // Each file extension can only be associated to one language. If a specific language is not specified, then the
38+ // following list of default file extensions will be used:
39+ // javascript: ['.js', '.cjs', '.mjs']
40+ // typescript: ['.ts']
41+ file_extensions : FileExtensionsObject
3942
4043 // (INTERNAL USE ONLY) Copy of the code analyzer config root.
4144 config_root : string
4245}
4346
47+ type FileExtensionsObject = {
48+ javascript : string [ ] ,
49+ typescript : string [ ]
50+ } ;
51+
4452export const DEFAULT_CONFIG : ESLintEngineConfig = {
4553 eslint_config_file : undefined ,
4654 eslint_ignore_file : undefined ,
4755 auto_discover_eslint_config : false ,
4856 disable_javascript_base_config : false ,
4957 disable_lwc_base_config : false ,
5058 disable_typescript_base_config : false ,
51- javascript_file_extensions : [ '.js' , '.cjs' , '.mjs' ] ,
52- typescript_file_extensions : [ '.ts' ] ,
59+ file_extensions : {
60+ javascript : [ '.js' , '.cjs' , '.mjs' ] ,
61+ typescript : [ '.ts' ]
62+ } ,
5363 config_root : process . cwd ( ) // INTERNAL USE ONLY
5464}
5565
@@ -86,15 +96,10 @@ export const ESLINT_ENGINE_CONFIG_DESCRIPTION: ConfigDescription = {
8696 valueType : "boolean" ,
8797 defaultValue : DEFAULT_CONFIG . disable_typescript_base_config
8898 } ,
89- javascript_file_extensions : {
90- descriptionText : getMessage ( 'ConfigFieldDescription_javascript_file_extensions' ) ,
91- valueType : "array" ,
92- defaultValue : DEFAULT_CONFIG . javascript_file_extensions
93- } ,
94- typescript_file_extensions : {
95- descriptionText : getMessage ( 'ConfigFieldDescription_typescript_file_extensions' ) ,
96- valueType : "array" ,
97- defaultValue : DEFAULT_CONFIG . typescript_file_extensions
99+ file_extensions : {
100+ descriptionText : getMessage ( 'ConfigFieldDescription_file_extensions' ) ,
101+ valueType : "object" ,
102+ defaultValue : DEFAULT_CONFIG . file_extensions
98103 }
99104 }
100105}
@@ -108,7 +113,6 @@ export const LEGACY_ESLINT_IGNORE_FILE: string = '.eslintignore';
108113
109114export function validateAndNormalizeConfig ( configValueExtractor : ConfigValueExtractor ) : ESLintEngineConfig {
110115 const eslintConfigValueExtractor : ESLintEngineConfigValueExtractor = new ESLintEngineConfigValueExtractor ( configValueExtractor ) ;
111- const [ jsExts , tsExts ] = eslintConfigValueExtractor . extractFileExtensionsValues ( ) ;
112116 return {
113117 config_root : configValueExtractor . getConfigRoot ( ) , // INTERNAL USE ONLY
114118 eslint_config_file : eslintConfigValueExtractor . extractESLintConfigFileValue ( ) ,
@@ -117,8 +121,7 @@ export function validateAndNormalizeConfig(configValueExtractor: ConfigValueExtr
117121 disable_javascript_base_config : eslintConfigValueExtractor . extractBooleanValue ( 'disable_javascript_base_config' ) ,
118122 disable_lwc_base_config : eslintConfigValueExtractor . extractBooleanValue ( 'disable_lwc_base_config' ) ,
119123 disable_typescript_base_config : eslintConfigValueExtractor . extractBooleanValue ( 'disable_typescript_base_config' ) ,
120- javascript_file_extensions : jsExts ,
121- typescript_file_extensions : tsExts
124+ file_extensions : eslintConfigValueExtractor . extractFileExtensionsValue ( ) ,
122125 } ;
123126}
124127
@@ -150,27 +153,45 @@ class ESLintEngineConfigValueExtractor {
150153 return eslintIgnoreFile ;
151154 }
152155
153- extractFileExtensionsValues ( ) : string [ ] [ ] {
154- const jsExtsField : string = 'javascript_file_extensions' ;
155- const tsExtsField : string = 'typescript_file_extensions' ;
156- const jsExts : string [ ] = makeUnique ( this . extractExtensionsValue ( jsExtsField , DEFAULT_CONFIG . javascript_file_extensions ) ! ) ;
157- const tsExts : string [ ] = makeUnique ( this . extractExtensionsValue ( tsExtsField , DEFAULT_CONFIG . typescript_file_extensions ) ! ) ;
156+ extractFileExtensionsValue ( ) : FileExtensionsObject {
157+ if ( ! this . delegateExtractor . hasValueDefinedFor ( 'file_extensions' ) ) {
158+ return DEFAULT_CONFIG . file_extensions ;
159+ }
160+
161+ const fileExtsObjExtractor : ConfigValueExtractor = this . delegateExtractor . extractObjectAsExtractor ( 'file_extensions' ) ;
162+
163+ // Validate languages
164+ const validLanguages : string [ ] = Object . keys ( DEFAULT_CONFIG . file_extensions ) ;
165+ for ( const key of fileExtsObjExtractor . getKeys ( ) ) {
166+ // Note: In the future we may want to make the languages case-insensitive. Right now it is a little tricky
167+ // because the extract* methods (like extractArray) look for the exact key name.
168+ if ( ! ( validLanguages . includes ( key ) ) ) {
169+ throw new Error ( getMessage ( 'InvalidFieldKeyForObject' , fileExtsObjExtractor . getFieldPath ( ) , key , validLanguages . join ( ', ' ) ) )
170+ }
171+ }
172+
173+ // Validate file extension patterns
174+ const extractExtensionsValue = function ( fieldName : string , defaultValue : string [ ] ) : string [ ] {
175+ const fileExts : string [ ] = fileExtsObjExtractor . extractArray ( fieldName , ValueValidator . validateString , defaultValue ) ! ;
176+ fileExts . map ( ( fileExt , i ) => validateStringMatches (
177+ ESLintEngineConfigValueExtractor . FILE_EXT_PATTERN , fileExt , `${ fileExtsObjExtractor . getFieldPath ( fieldName ) } [${ i } ]` ) ) ;
178+ return makeUnique ( fileExts ) ;
179+ }
180+ const fileExtsObj : FileExtensionsObject = {
181+ javascript : extractExtensionsValue ( 'javascript' , DEFAULT_CONFIG . file_extensions . javascript ) ,
182+ typescript : extractExtensionsValue ( 'typescript' , DEFAULT_CONFIG . file_extensions . typescript )
183+ }
158184
159- const allExts : string [ ] = jsExts . concat ( tsExts ) ;
185+ // Validate that there is no file extension listed with multiple languages
186+ const allExts : string [ ] = fileExtsObj . javascript . concat ( fileExtsObj . typescript ) ;
160187 if ( allExts . length != ( new Set ( allExts ) ) . size ) {
161188 const currentValuesString : string =
162- ` ${ this . delegateExtractor . getFieldPath ( jsExtsField ) } : ${ JSON . stringify ( jsExts ) } \n` +
163- ` ${ this . delegateExtractor . getFieldPath ( tsExtsField ) } : ${ JSON . stringify ( tsExts ) } ` ;
189+ ` ${ fileExtsObjExtractor . getFieldPath ( 'javascript' ) } : ${ JSON . stringify ( fileExtsObj . javascript ) } \n` +
190+ ` ${ fileExtsObjExtractor . getFieldPath ( 'typescript' ) } : ${ JSON . stringify ( fileExtsObj . typescript ) } ` ;
164191 throw new Error ( getMessage ( 'ConfigStringArrayValuesMustNotShareElements' , currentValuesString ) ) ;
165192 }
166193
167- return [ jsExts , tsExts ] ;
168- }
169-
170- extractExtensionsValue ( fieldName : string , defaultValue : string [ ] ) : string [ ] {
171- const fileExts : string [ ] = this . delegateExtractor . extractArray ( fieldName , ValueValidator . validateString , defaultValue ) ! ;
172- return fileExts . map ( ( fileExt , i ) => validateStringMatches (
173- ESLintEngineConfigValueExtractor . FILE_EXT_PATTERN , fileExt , `${ this . delegateExtractor . getFieldPath ( fieldName ) } [${ i } ]` ) ) ;
194+ return fileExtsObj ;
174195 }
175196
176197 extractBooleanValue ( field_name : string ) : boolean {
0 commit comments