(GH-579) Add `dsc lint` command by Gijsreyn · Pull Request #1768 · PowerShell/DSC · GitHub
Skip to content

(GH-579) Add dsc lint command - #1768

Draft
Gijs Reijn (Gijsreyn) wants to merge 1 commit into
PowerShell:mainfrom
Gijsreyn:gh-591/main/add-dsc-lint-command
Draft

Gijs Reijn (Gijsreyn) wants to merge 1 commit into
PowerShell:mainfrom
Gijsreyn:gh-591/main/add-dsc-lint-command

Conversation

@Gijsreyn

Copy link
Copy Markdown
Collaborator

PR Summary

This change:

  • Adds the dsc lint command. It checks a configuration document for problems without applying
    it, reads one or more files or an inline document, and always writes a result with the
    diagnostics, a summary, whether the document is valid, and whether discovery ran. It exits with
    code 5 when a diagnostic reaches the level set by the fail-on option, which defaults to error.
  • Adds a lint engine to dsc-lib. It parses the document, validates it against the configuration
    schema and reports every violation with its JSON pointer, then indexes every expression once and
    runs 43 rules across seven categories plus four fixed rules for parse, schema, unsupported
    document type, and discovery failures. Rule levels can be changed per run or in a new lint
    section of the settings file, with the policy file taking precedence.
  • Adds the parse-only entry points the rules need, which the runtime now shares: a parse method on
    the expression parser, argument-count and argument-kind checks on the function dispatcher
    extracted from invoke, a schema validator that returns every violation instead of the first, and
    a raw-text input reader in the CLI so a syntax error becomes a diagnostic instead of an early
    exit. Parameter merging and parameter parsing move into helpers shared by the config and lint
    commands.
  • Adds the lint result and lint rules schema types, registers them for export and in the schema
    integration test, and adds reference docs, a rules catalog, a changelog entry, 68 Rust unit
    tests, and 43 Pester tests including a sweep of the shipped examples.
  • Addresses static linting for configuration #591 and config validate command needs to check for duplicate resource names in configuration document #842.

PR Context

DSC only finds most document mistakes when it runs the document. The hidden validate subcommand
is only called by the Group, Assertion and Include resources, reports a bare boolean, exits zero
even when invalid, and discovers resources before validating anything. Mistakes such as a
dependency on a resource that doesn't exist, a reference to a resource that hasn't run, a
condition written as a plain string, or a secure parameter with a hard-coded default are reported
late, without a location, or not at all.

Remarks on the design

  • Discovery is on by default, matching bicep lint and the hidden validate command, with a flag to
    skip it. Rules that depend on discovery report warnings rather than errors, so a build agent
    without the resources still passes. The linter never invokes a resource; property checks only
    use the schema embedded in the manifest.
  • A document that doesn't parse still produces the structured result and exits with code 5, so a
    pipeline always has something to show. The valid flag doesn't depend on the fail-on level.
  • Diagnostics carry JSON pointers. Only parse errors carry a line and column, because the YAML and
    JSON parsers keep no positions for values. The location field is in the schema from the start so
    adding positions later isn't a breaking change.
  • Rule names follow the Bicep linter where the concept is shared. The reference-without-depends-on
    rule exists because DSC doesn't auto-order on references the way ARM does. Duplicate and
    dependency rules skip copy loops, whose names are only known at runtime. Secure default values
    are never echoed in messages.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Secure values can leak into diagnostics and logs, and validation mismatches produce incorrect lint results.

11 open issues
What changed in this PR

Adds dsc lint to detect configuration problems before execution, including duplicate resources and system-root usage checks requested in #842 and #591.

Changes:

  • Adds configurable lint rules, structured diagnostics, discovery checks, and failure thresholds.
  • Shares parsing and validation helpers with runtime code.
  • Adds schemas, documentation, localization, and automated tests.
File Description
xtask/​src/​schemas/​export.rs Registers lint schemas for export.
lib/​dsc-lib/​tests/​integration/​schemas/​schema_for.rs Tests lint schema generation.
lib/​dsc-lib/​src/​util.rs Adds JSON/YAML value parsing.
lib/​dsc-lib/​src/​parser/​mod.rs Adds parse-only expression support.
lib/​dsc-lib/​src/​parser/​functions.rs Exposes function arguments.
lib/​dsc-lib/​src/​parser/​expressions.rs Exposes expression structure.
lib/​dsc-lib/​src/​lint/​rules/​resource.rs Adds discovery-dependent resource checks.
lib/​dsc-lib/​src/​lint/​rules/​parameter.rs Adds parameter and variable checks.
lib/​dsc-lib/​src/​lint/​rules/​mod.rs Collects lint rules.
lib/​dsc-lib/​src/​lint/​rules/​expression.rs Adds expression validation rules.
lib/​dsc-lib/​src/​lint/​rules/​environment.rs Checks system-root usage and paths.
lib/​dsc-lib/​src/​lint/​rules/​document.rs Checks document structure and duplicates.
lib/​dsc-lib/​src/​lint/​rules/​deprecation.rs Checks deprecated and unsupported features.
lib/​dsc-lib/​src/​lint/​rules/​dependency.rs Checks dependencies and references.
lib/​dsc-lib/​src/​lint/​rule.rs Defines rules and diagnostic collection.
lib/​dsc-lib/​src/​lint/​pointer.rs Adds JSON pointer utilities.
lib/​dsc-lib/​src/​lint/​options.rs Defines lint options and settings.
lib/​dsc-lib/​src/​lint/​expression_index.rs Indexes expressions for rule evaluation.
lib/​dsc-lib/​src/​lint/​diagnostic.rs Defines diagnostics and combined results.
lib/​dsc-lib/​src/​lint/​context.rs Provides shared rule context.
lib/​dsc-lib/​src/​lib.rs Exposes the lint module.
lib/​dsc-lib/​src/​functions/​mod.rs Adds non-executing argument validation.
lib/​dsc-lib/​src/​dscresources/​dscresource.rs Collects all schema violations.
lib/​dsc-lib/​src/​dscerror.rs Integrates lint errors.
lib/​dsc-lib/​src/​configure/​mod.rs Shares adapter-directive lookup.
lib/​dsc-lib/​locales/​schemas.outputs.yaml Describes lint output schemas.
lib/​dsc-lib/​locales/​en-us.toml Adds lint diagnostic messages.
dsc/​tests/​dsc_lint.tests.ps1 Adds CLI lint coverage.
dsc/​src/​util.rs Adds raw input reading and schemas.
dsc/​src/​subcommand.rs Implements lint command handling.
dsc/​src/​main.rs Dispatches lint and shares parameter merging.
dsc/​src/​args.rs Defines lint arguments and schema options.
dsc/​locales/​en-us.toml Adds CLI help and messages.
dsc/​dsc.settings.json Adds lint settings.
dsc/​dsc_default.settings.json Adds default lint settings.
docs/​reference/​schemas/​outputs/​lint/​rules.md Documents rule metadata output.
docs/​reference/​schemas/​outputs/​lint/​result.md Documents lint results.
docs/​reference/​cli/​lint/​rules.md Documents the rule catalog.
docs/​reference/​cli/​lint/​index.md Documents command usage and settings.
docs/​reference/​cli/​index.md Links the lint command reference.
CHANGELOG.md Records the new capability.

Review effort: Balanced

/// * `DscError::Validation` - The schema failed to compile
pub fn validate_json_all(source: &str, schema: &Value, json: &Value) -> Result<Vec<SchemaViolation>, DscError> {
debug!("{}: {source}", t!("dscresources.dscresource.validatingSchema"));
trace!("{}", t!("dscresources.dscresource.json", json = json));
Comment on lines +110 to +111
expression = site.text,
error = LintContext::error_message(err)
Comment thread dsc/src/args.rs
Comment on lines +100 to +101
#[clap(name = "lint", about = t!("args.lintAbout").to_string())]
Lint {
Comment on lines +1126 to +1127
secureValueInOutputDescription = "Outputs shouldn't expose secure values; DSC skips such outputs at runtime."
secureValueInOutput = "Output '%{name}' exposes the secure value from %{source}, so DSC skips it at runtime"
Comment on lines +124 to +127
let scope = ExpressionScope::Resource(position);
for key in ["name", "condition", "dependsOn", "properties"] {
if let Some(member) = resource.get(key) {
index.walk(member, &site.pointer.push(key), &scope, parser);
Comment on lines +190 to +191
for (pointer, key) in literal_dependencies(context, index).into_iter().chain(reference_targets(context, index)) {
match resolve(context, &group, &key) {
if value.as_str().is_some_and(LintContext::is_expression) {
continue;
}
if let Err(reason) = check_value(name, value, parameter) {
Comment on lines +440 to +442
if let Err(reason) = check_value(name, value, parameter) {
sink.report(&parameter_pointer(name), t!("lint.rules.parameter.parameterValueType", name = name, reason = reason).to_string());
}
Comment on lines +203 to +206
let pointer = base.join(&violation.instance_path);
let in_expression = context.expressions.sites().iter().any(|expression| {
expression.scope == ExpressionScope::Resource(index) && pointer.starts_with(&expression.pointer)
});
Comment on lines +204 to +206
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants