Skip to content

Commit 2046b8a

Browse files
authored
Allow for full cfn-lint configurations (#304)
1 parent 0e6f266 commit 2046b8a

File tree

9 files changed

+338
-18
lines changed

9 files changed

+338
-18
lines changed

src/services/cfnLint/CfnLintService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class CfnLintService implements SettingsConfigurable, Closeable {
8383
) {
8484
this.settings = DefaultSettings.diagnostics.cfnLint;
8585
this.delayer = delayer ?? new Delayer<void>(this.settings.delayMs);
86-
this.workerManager = workerManager ?? new PyodideWorkerManager(this.settings.initialization);
86+
this.workerManager = workerManager ?? new PyodideWorkerManager(this.settings.initialization, this.settings);
8787
}
8888

8989
configure(settingsManager: ISettingsSubscriber): void {
@@ -103,6 +103,7 @@ export class CfnLintService implements SettingsConfigurable, Closeable {
103103

104104
private onSettingsChanged(newSettings: CfnLintSettings): void {
105105
this.settings = newSettings;
106+
this.workerManager.updateSettings(newSettings);
106107
// Note: Delayer delay is immutable, set at construction time
107108
// The new delayMs will be used for future operations that check this.settings.delayMs
108109
}

src/services/cfnLint/PyodideWorkerManager.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import path from 'path';
22
import { Worker } from 'worker_threads';
33
import { PublishDiagnosticsParams } from 'vscode-languageserver';
44
import { CloudFormationFileType } from '../../document/Document';
5-
import { CfnLintInitializationSettings } from '../../settings/Settings';
5+
import { CfnLintInitializationSettings, CfnLintSettings } from '../../settings/Settings';
66
import { LoggerFactory } from '../../telemetry/LoggerFactory';
77
import { retryWithExponentialBackoff } from '../../utils/Retry';
88
import { WorkerNotInitializedError } from './CfnLintErrors';
@@ -33,6 +33,7 @@ export class PyodideWorkerManager {
3333

3434
constructor(
3535
private readonly retryConfig: CfnLintInitializationSettings,
36+
private cfnLintSettings: CfnLintSettings,
3637
private readonly log = LoggerFactory.getLogger(PyodideWorkerManager),
3738
) {}
3839

@@ -169,15 +170,29 @@ export class PyodideWorkerManager {
169170
uri: string,
170171
fileType: CloudFormationFileType,
171172
): Promise<PublishDiagnosticsParams[]> {
172-
return await this.executeTask<PublishDiagnosticsParams[]>('lint', { content, uri, fileType });
173+
return await this.executeTask<PublishDiagnosticsParams[]>('lint', {
174+
content,
175+
uri,
176+
fileType,
177+
settings: this.cfnLintSettings,
178+
});
173179
}
174180

175181
public async lintFile(
176182
path: string,
177183
uri: string,
178184
fileType: CloudFormationFileType,
179185
): Promise<PublishDiagnosticsParams[]> {
180-
return await this.executeTask<PublishDiagnosticsParams[]>('lintFile', { path, uri, fileType });
186+
return await this.executeTask<PublishDiagnosticsParams[]>('lintFile', {
187+
path,
188+
uri,
189+
fileType,
190+
settings: this.cfnLintSettings,
191+
});
192+
}
193+
194+
public updateSettings(settings: CfnLintSettings): void {
195+
this.cfnLintSettings = settings;
181196
}
182197

183198
public async mountFolder(fsDir: string, mountDir: string): Promise<void> {

src/services/cfnLint/pyodide-worker.ts

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ if (parentPort) {
5454
payload.content as string,
5555
payload.uri as string,
5656
payload.fileType as CloudFormationFileType,
57+
payload.settings as Record<string, unknown>,
5758
);
5859
break;
5960
}
@@ -62,6 +63,7 @@ if (parentPort) {
6263
payload.path as string,
6364
payload.uri as string,
6465
payload.fileType as CloudFormationFileType,
66+
payload.settings as Record<string, unknown>,
6567
);
6668
break;
6769
}
@@ -247,30 +249,71 @@ async function initializePyodide(): Promise<InitializeResult> {
247249
248250
return results
249251
250-
def lint_str(template_str, uri, template_args=None):
252+
def parse_cfn_lint_settings(settings):
253+
"""Parse cfn-lint settings into ManualArgs format"""
254+
config = {}
255+
if not settings:
256+
return config
257+
258+
if settings.get('ignoreChecks'):
259+
config['ignore_checks'] = settings['ignoreChecks']
260+
if settings.get('includeChecks'):
261+
config['include_checks'] = settings['includeChecks']
262+
if settings.get('mandatoryChecks'):
263+
config['mandatory_checks'] = settings['mandatoryChecks']
264+
if settings.get('includeExperimental'):
265+
config['include_experimental'] = settings['includeExperimental']
266+
if settings.get('configureRules'):
267+
# Parse configure rules from string format "RuleId:key=value"
268+
configure_rules = {}
269+
for rule_config in settings['configureRules']:
270+
if ':' in rule_config:
271+
rule_id, config_str = rule_config.split(':', 1)
272+
if '=' in config_str:
273+
key, value = config_str.split('=', 1)
274+
if rule_id not in configure_rules:
275+
configure_rules[rule_id] = {}
276+
# Convert string values to appropriate types
277+
if value.lower() == 'true':
278+
configure_rules[rule_id][key] = True
279+
elif value.lower() == 'false':
280+
configure_rules[rule_id][key] = False
281+
else:
282+
try:
283+
configure_rules[rule_id][key] = int(value)
284+
except ValueError:
285+
configure_rules[rule_id][key] = value
286+
if configure_rules:
287+
config['configure_rules'] = configure_rules
288+
if settings.get('regions'):
289+
config['regions'] = settings['regions']
290+
return config
291+
292+
def lint_str(template_str, uri, settings=None):
251293
"""
252294
Lint a CloudFormation template string and return LSP diagnostics
253295
254296
Args:
255297
template_str (str): CloudFormation template as a string
256298
uri (str): Document URI
257-
template_args (dict, optional): Additional template arguments
299+
settings (dict, optional): cfn-lint settings
258300
259301
Returns:
260302
dict: LSP PublishDiagnosticsParams
261303
"""
304+
config = parse_cfn_lint_settings(settings)
305+
return match_to_diagnostics(lint(template_str, config=ManualArgs(**config) if config else None), uri)
262306
263-
return match_to_diagnostics(lint(template_str), uri)
264-
265-
def lint_uri(lint_path, uri, lint_type, template_args=None):
266-
args = ManualArgs()
307+
def lint_uri(lint_path, uri, lint_type, settings=None):
308+
config = parse_cfn_lint_settings(settings)
267309
path = Path(lint_path)
310+
268311
if lint_type == "template":
269-
args["templates"] = [str(path)]
312+
config["templates"] = [str(path)]
270313
elif lint_type == "gitsync-deployment":
271-
args["deployment_files"] = [str(path)]
314+
config["deployment_files"] = [str(path)]
272315
273-
return match_to_diagnostics(lint_by_config(args), uri)
316+
return match_to_diagnostics(lint_by_config(ManualArgs(**config)), uri)
274317
`);
275318

276319
// Create result object first
@@ -309,6 +352,7 @@ async function lintTemplate(
309352
content: string,
310353
uri: string,
311354
_fileType: CloudFormationFileType,
355+
settings?: Record<string, unknown>,
312356
): Promise<PublishDiagnosticsParams[]> {
313357
if (!initialized || !pyodide) {
314358
throw new Error('Pyodide not initialized');
@@ -319,9 +363,11 @@ async function lintTemplate(
319363
const pyUri = pyodide.toPy(uri);
320364
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
321365
const pyContent = pyodide.toPy(content.replaceAll('"""', '\\"\\"\\"'));
366+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
367+
const pySettings = pyodide.toPy(settings ?? {});
322368

323369
// Execute Python code and get result
324-
const pythonCode = `lint_str(r"""${pyContent}""", r"""${pyUri}""")`;
370+
const pythonCode = `lint_str(r"""${pyContent}""", r"""${pyUri}""", ${pySettings})`;
325371
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
326372
const result = await pyodide.runPythonAsync(pythonCode);
327373

@@ -333,13 +379,17 @@ async function lintFile(
333379
path: string,
334380
uri: string,
335381
fileType: CloudFormationFileType,
382+
settings?: Record<string, unknown>,
336383
): Promise<PublishDiagnosticsParams[]> {
337384
if (!initialized || !pyodide) {
338385
throw new Error('Pyodide not initialized');
339386
}
340387

388+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
389+
const pySettings = pyodide.toPy(settings ?? {});
390+
341391
// Execute Python code and get result
342-
const pythonCode = `lint_uri(r"""${path}""", r"""${uri}""", r"""${fileType}""")`;
392+
const pythonCode = `lint_uri(r"""${path}""", r"""${uri}""", r"""${fileType}""", ${pySettings})`;
343393
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
344394
const result = await pyodide.runPythonAsync(pythonCode);
345395

src/settings/Settings.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ export type CfnLintSettings = Toggleable<{
2929
delayMs: number;
3030
lintOnChange: boolean;
3131
initialization: CfnLintInitializationSettings;
32+
ignoreChecks: readonly string[];
33+
includeChecks: readonly string[];
34+
mandatoryChecks: readonly string[];
35+
includeExperimental: boolean;
36+
configureRules: readonly string[];
37+
regions: readonly string[];
38+
customRules: readonly string[];
39+
appendRules: readonly string[];
40+
overrideSpec: string;
41+
registrySchemas: readonly string[];
3242
}>;
3343

3444
export type GuardSettings = Toggleable<{
@@ -87,6 +97,16 @@ export const DefaultSettings: DeepReadonly<Settings> = {
8797
backoffMultiplier: 2,
8898
totalTimeoutMs: 120_000, // 2 minutes total timeout
8999
},
100+
ignoreChecks: [],
101+
includeChecks: [],
102+
mandatoryChecks: [],
103+
includeExperimental: false,
104+
configureRules: [],
105+
regions: [],
106+
customRules: [],
107+
appendRules: [],
108+
overrideSpec: '',
109+
registrySchemas: [],
90110
},
91111
cfnGuard: {
92112
enabled: true,

src/settings/SettingsParser.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,58 @@ function createCfnLintInitializationSchema(defaults: Settings['diagnostics']['cf
4747
}
4848

4949
function createCfnLintSchema(defaults: Settings['diagnostics']['cfnLint']) {
50+
const customizationSchema = z
51+
.object({
52+
ignoreChecks: z.array(z.string()).readonly().optional(),
53+
includeChecks: z.array(z.string()).readonly().optional(),
54+
mandatoryChecks: z.array(z.string()).readonly().optional(),
55+
includeExperimental: z.boolean().optional(),
56+
configureRules: z.array(z.string()).readonly().optional(),
57+
regions: z.array(z.string()).readonly().optional(),
58+
customRules: z.array(z.string()).readonly().optional(),
59+
appendRules: z.array(z.string()).readonly().optional(),
60+
overrideSpec: z.string().optional(),
61+
registrySchemas: z.array(z.string()).readonly().optional(),
62+
})
63+
.optional();
64+
5065
return z
5166
.object({
5267
enabled: z.boolean().default(defaults.enabled),
5368
delayMs: z.number().default(defaults.delayMs),
5469
lintOnChange: z.boolean().default(defaults.lintOnChange),
5570
initialization: createCfnLintInitializationSchema(defaults.initialization),
71+
ignoreChecks: z.array(z.string()).readonly().default(defaults.ignoreChecks),
72+
includeChecks: z.array(z.string()).readonly().default(defaults.includeChecks),
73+
mandatoryChecks: z.array(z.string()).readonly().default(defaults.mandatoryChecks),
74+
includeExperimental: z.boolean().default(defaults.includeExperimental),
75+
configureRules: z.array(z.string()).readonly().default(defaults.configureRules),
76+
regions: z.array(z.string()).readonly().default(defaults.regions),
77+
customRules: z.array(z.string()).readonly().default(defaults.customRules),
78+
appendRules: z.array(z.string()).readonly().default(defaults.appendRules),
79+
overrideSpec: z.string().default(defaults.overrideSpec),
80+
registrySchemas: z.array(z.string()).readonly().default(defaults.registrySchemas),
81+
customization: customizationSchema,
82+
})
83+
.transform((data) => {
84+
// Merge customization settings into main object
85+
if (data.customization) {
86+
const { customization, ...rest } = data;
87+
return {
88+
...rest,
89+
ignoreChecks: customization.ignoreChecks ?? data.ignoreChecks,
90+
includeChecks: customization.includeChecks ?? data.includeChecks,
91+
mandatoryChecks: customization.mandatoryChecks ?? data.mandatoryChecks,
92+
includeExperimental: customization.includeExperimental ?? data.includeExperimental,
93+
configureRules: customization.configureRules ?? data.configureRules,
94+
regions: customization.regions ?? data.regions,
95+
customRules: customization.customRules ?? data.customRules,
96+
appendRules: customization.appendRules ?? data.appendRules,
97+
overrideSpec: customization.overrideSpec ?? data.overrideSpec,
98+
registrySchemas: customization.registrySchemas ?? data.registrySchemas,
99+
};
100+
}
101+
return data;
56102
})
57103
.default(defaults);
58104
}

0 commit comments

Comments
 (0)