Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

- Added `blocks: Vec<String>` to `ffi::MozAdsRequestOptions`, `AdsClient::request*_ads`, `MARSClient::fetch_ads`, `mars::AdRequest`, and `mars::AdRequest::try_new`. This is serialized and passed to MARS so that it can remove blocks server-side.

### Nimbus

- Add `nimbus-fml lint`, which checks a manifest against feature design lints covering metadata, descriptions, naming, and feature shape. Findings are warnings and don't affect code generation; `--error-on-warning` fails the run, for CI. A `no-lint` list on a feature or at the top of a manifest excuses it from the lints it names, so older versions of `nimbus-fml` will reject a manifest that uses one. `nimbus-fml validate` no longer reports feature metadata warnings; run `nimbus-fml lint` for those. ([Bug 2053531](https://bugzilla.mozilla.org/show_bug.cgi?id=2053531))

# v156.0 (_2026-08-27_)

## ✨ What's Changed ✨
Expand Down
19 changes: 19 additions & 0 deletions components/support/nimbus-fml/fixtures/fe/lints/included.fml.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
# Included by including.fml.yaml; excuses what it defines from a lint.
about:
description: Fixture for the lint tests.
no-lint:
- MISSING_META_BUG
features:
included-feature:
description: A feature defined in a file that another file includes.
contacts:
- jdoe@example.com
documentation:
- name: User documentation
url: https://example.com/included-feature
variables:
enabled:
description: Whether the included feature does anything at all.
type: Boolean
default: false
22 changes: 22 additions & 0 deletions components/support/nimbus-fml/fixtures/fe/lints/including.fml.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
# Includes a file that carries its own top level `no-lint` list.
about:
description: Fixture for the lint tests.
channels:
- release
includes:
- included.fml.yaml
features:
including-feature:
description: A feature defined in the file that does the including.
contacts:
- jdoe@example.com
documentation:
- name: User documentation
url: https://example.com/including-feature
meta-bug: https://example.com/bugs
variables:
enabled:
description: Whether the including feature does anything at all.
type: Boolean
default: false
20 changes: 20 additions & 0 deletions components/support/nimbus-fml/fixtures/fe/lints/invalid.fml.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
---
# `broken-feature` defaults a boolean to a string, on every channel, so this
# manifest is invalid whichever one is generated. Linting it would only report
# nonsense.
version: 1.0
about:
description: A manifest which can't be linted, because it isn't valid.
channels:
- release
features:
broken-feature:
description: A feature whose default value doesn't match its type.
variables:
enabled:
description: Whether the feature is enabled at all.
type: Boolean
default: "yes"
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
# Trips as many lints as fit into one file.
about:
description: Fixture for the lint tests.
channels:
- release
features:
myBadFeature:
description: Bad
variables:
hide-toolbar:
description: TODO
type: Boolean
default: false
myBadFeature-mode:
description: Which of the layouts the feature uses.
type: String
default: compact
section-list:
description: The sections shown to the user, in the order they appear.
type: List<String>
default: []
deep:
description: A value that has to be written out four levels deep.
type: Outer
default: {}
objects:
Outer:
description: The outermost object.
fields:
middle:
description: The object in the middle.
type: Middle
default: {}
Middle:
description: The object in the middle.
fields:
inner:
description: The innermost object.
type: Inner
default: {}
Inner:
description: The innermost object.
fields:
label:
description: The label shown to the user.
type: String
default: ""
unusedObject:
description: An object that no feature refers to.
fields:
label:
description: The label shown to the user.
type: String
default: ""
enums:
OnlyOne:
description: An enum that doesn't offer a choice.
variants:
onlyVariant:
description: The only variant there is.
57 changes: 57 additions & 0 deletions components/support/nimbus-fml/fixtures/fe/lints/sparse.fml.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
# Trips the lints that `needs-work.fml.yaml` can't: a feature with nothing to
# configure, one with far too much, an unwritten description, and a contact that
# isn't an address.
about:
description: Fixture for the lint tests.
channels:
- release
features:
nothing-to-change:
description: A feature an experiment can enrol users in but not configure.
meta-bug: https://bugzilla.mozilla.org/show_bug.cgi?id=2053531
contacts:
- the-toolbar-team
documentation:
- name: User documentation
url: https://example.com/nothing-to-change
variables: {}
everything-at-once:
description: ""
meta-bug: https://bugzilla.mozilla.org/show_bug.cgi?id=2053531
contacts:
- jdoe@example.com
documentation:
- name: User documentation
url: https://example.com/everything-at-once
variables:
enabled:
description: Whether any of the rest of this feature is used.
type: Boolean
default: false
alpha: { description: The first of the many knobs this feature has., type: Int, default: 1 }
bravo: { description: The second of the many knobs this feature has., type: Int, default: 2 }
charlie: { description: The third of the many knobs this feature has., type: Int, default: 3 }
delta: { description: The fourth of the many knobs this feature has., type: Int, default: 4 }
echo: { description: The fifth of the many knobs this feature has., type: Int, default: 5 }
foxtrot: { description: The sixth of the many knobs this feature has., type: Int, default: 6 }
golf: { description: The seventh of the many knobs this feature has., type: Int, default: 7 }
hotel: { description: The eighth of the many knobs this feature has., type: Int, default: 8 }
india: { description: The ninth of the many knobs this feature has., type: Int, default: 9 }
juliett: { description: The tenth of the many knobs this feature has., type: Int, default: 10 }
kilo: { description: The eleventh of the many knobs this feature has., type: Int, default: 11 }
lima: { description: The twelfth of the many knobs this feature has., type: Int, default: 12 }
mike: { description: The thirteenth of the many knobs this feature has., type: Int, default: 13 }
november: { description: The fourteenth of the many knobs this feature has., type: Int, default: 14 }
oscar: { description: The fifteenth of the many knobs this feature has., type: Int, default: 15 }
papa: { description: The sixteenth of the many knobs this feature has., type: Int, default: 16 }
quebec: { description: The seventeenth of the many knobs this feature has., type: Int, default: 17 }
romeo: { description: The eighteenth of the many knobs this feature has., type: Int, default: 18 }
sierra: { description: The nineteenth of the many knobs this feature has., type: Int, default: 19 }
tango: { description: The twentieth of the many knobs this feature has., type: Int, default: 20 }
uniform: { description: The twenty-first of the many knobs this feature has., type: Int, default: 21 }
victor: { description: The twenty-second of the many knobs this feature has., type: Int, default: 22 }
whiskey: { description: The twenty-third of the many knobs this feature has., type: Int, default: 23 }
xray: { description: The twenty-fourth of the many knobs this feature has., type: Int, default: 24 }
yankee: { description: The twenty-fifth of the many knobs this feature has., type: Int, default: 25 }
zulu: { description: The twenty-sixth of the many knobs this feature has., type: Int, default: 26 }
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
# Excuses itself from some lints, at both levels.
about:
description: Fixture for the lint tests.
channels:
- release
no-lint:
- MISSING_META_BUG
- NOT_A_REAL_FILE_LINT
features:
legacy-feature:
description: A feature that was designed before there were lints to nudge it.
no-lint:
- MISSING_ENABLED_VARIABLE
- NOT_A_REAL_LINT
contacts:
- jdoe@example.com
documentation:
- name: User documentation
url: https://example.com/legacy-feature
variables:
max-rows:
description: The largest number of rows the list is allowed to grow to.
type: Int
default: 3
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
# Trips no lints.
about:
description: Fixture for the lint tests.
channels:
- release
features:
toolbar-redesign:
description: The redesigned toolbar shown at the bottom of the browser screen.
meta-bug: https://bugzilla.mozilla.org/show_bug.cgi?id=2053531
contacts:
- jdoe@example.com
documentation:
- name: User documentation
url: https://example.com/toolbar-redesign
variables:
enabled:
description: Whether the redesigned toolbar is shown instead of the old one.
type: Boolean
default: false
button-style:
description: How the buttons in the toolbar are drawn.
type: ButtonStyle
default: outline
enums:
ButtonStyle:
description: The ways a toolbar button can be drawn.
variants:
outline:
description: The button is drawn as an outline only.
filled:
description: The button is drawn filled with the accent colour.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl From<FeatureManifest> for ManifestFrontEnd {
channels,
includes: Default::default(),
imports: Default::default(),
no_lint: Default::default(),
features,
legacy_types: None,
types: Types { enums, objects },
Expand Down
37 changes: 37 additions & 0 deletions components/support/nimbus-fml/src/command_line/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub enum Command {
/// Validate an FML configuration and all of its channels.
Validate(Validate),

/// Check an FML configuration against the Nimbus feature design lints.
Lint(Lint),

/// Print out all the channels to stdout, as JSON or one-per-line
Channels(Channels),

Expand Down Expand Up @@ -119,6 +122,40 @@ pub struct Validate {
pub loader_info: LoaderInfo,
}

#[derive(Args)]
pub struct Lint {
/// Sets the input file to use
#[arg(value_name = "INPUT", required_unless_present = "list")]
pub input: Option<String>,

#[command(flatten)]
pub loader_info: LoaderInfo,

/// Switch a lint off for this run. May be repeated.
#[arg(long, value_name = "LINT")]
pub allow: Vec<String>,

/// Turn a lint into an error for this run. May be repeated.
#[arg(long, value_name = "LINT")]
pub deny: Vec<String>,

/// Exit with an error if there are any warnings.
#[arg(long)]
pub error_on_warning: bool,

/// Also lint the features of imported manifests.
#[arg(long)]
pub include_imports: bool,

/// If present, then print the findings as JSON.
#[arg(long)]
pub json: bool,

/// Print the available lints and exit.
#[arg(long)]
pub list: bool,
}

#[derive(Args)]
pub struct Channels {
/// Sets the input file to use
Expand Down
12 changes: 12 additions & 0 deletions components/support/nimbus-fml/src/command_line/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub(crate) enum CliCmd {
GenerateSingleFileManifest(GenerateSingleFileManifestCmd),
FetchFile(LoaderConfig, String),
Validate(ValidateCmd),
Lint(LintCmd),
ListLints,
PrintChannels(PrintChannelsCmd),
PrintInfo(PrintInfoCmd),
}
Expand Down Expand Up @@ -48,6 +50,16 @@ pub(crate) struct ValidateCmd {
pub(crate) loader: LoaderConfig,
}

pub(crate) struct LintCmd {
pub(crate) manifest: String,
pub(crate) loader: LoaderConfig,
pub(crate) allow: Vec<String>,
pub(crate) deny: Vec<String>,
pub(crate) error_on_warning: bool,
pub(crate) include_imports: bool,
pub(crate) as_json: bool,
}

pub(crate) struct PrintChannelsCmd {
pub(crate) manifest: String,
pub(crate) loader: LoaderConfig,
Expand Down
25 changes: 24 additions & 1 deletion components/support/nimbus-fml/src/command_line/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use anyhow::Result;
use clap::Parser;
use commands::{
CliCmd, GenerateExperimenterManifestCmd, GenerateSingleFileManifestCmd, GenerateStructCmd,
PrintChannelsCmd, ValidateCmd,
LintCmd, PrintChannelsCmd, ValidateCmd,
};

use std::{collections::BTreeMap, ffi::OsString, path::Path};
Expand All @@ -39,6 +39,8 @@ fn process_command(cmd: &CliCmd) -> Result<()> {
}
CliCmd::FetchFile(files, nm) => workflows::fetch_file(files, nm)?,
CliCmd::Validate(params) => workflows::validate(params)?,
CliCmd::Lint(params) => workflows::lint(params)?,
CliCmd::ListLints => workflows::list_lints()?,
CliCmd::PrintChannels(params) => workflows::print_channels(params)?,
CliCmd::PrintInfo(params) => workflows::print_info(params)?,
};
Expand Down Expand Up @@ -68,6 +70,7 @@ where
cli::Command::Validate(cmd) => {
CliCmd::Validate(create_validate_command_from_cli(&cmd, cwd)?)
}
cli::Command::Lint(cmd) => create_lint_command_from_cli(&cmd, cwd)?,
cli::Command::Channels(cmd) => {
CliCmd::PrintChannels(create_print_channels_from_cli(&cmd, cwd)?)
}
Expand Down Expand Up @@ -176,6 +179,26 @@ fn create_validate_command_from_cli(cmd: &cli::Validate, cwd: &Path) -> Result<V
Ok(ValidateCmd { manifest, loader })
}

fn create_lint_command_from_cli(cmd: &cli::Lint, cwd: &Path) -> Result<CliCmd> {
if cmd.list {
return Ok(CliCmd::ListLints);
}

// clap has already checked that there is an input file if we're not listing.
let manifest = cmd.input.clone().unwrap_or_default();
let loader = create_loader(&manifest, &cmd.loader_info, cwd)?;

Ok(CliCmd::Lint(LintCmd {
manifest,
loader,
allow: cmd.allow.clone(),
deny: cmd.deny.clone(),
error_on_warning: cmd.error_on_warning,
include_imports: cmd.include_imports,
as_json: cmd.json,
}))
}

fn create_print_channels_from_cli(cmd: &cli::Channels, cwd: &Path) -> Result<PrintChannelsCmd> {
let manifest = cmd.input.clone();
let loader = create_loader(&cmd.input, &cmd.loader_info, cwd)?;
Expand Down
Loading