Skip to content

Commit 423e536

Browse files
committed
Implement package description checking
1 parent 52fdd22 commit 423e536

File tree

35 files changed

+362
-10
lines changed

35 files changed

+362
-10
lines changed

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ The following checks are performed when calling the binary:
8080
Evaluate Nixpkgs with `system` set to `x86_64-linux` and check that:
8181
- For each package directory, the `pkgs.${name}` attribute must be defined as `callPackage pkgs/by-name/${shard}/${name}/package.nix args` for some `args`.
8282
- For each package directory, `pkgs.lib.isDerivation pkgs.${name}` must be `true`.
83+
- For each top-level attribute, `meta.description` must:
84+
- Start with a capital letter
85+
- Not start with an article (a/an/the)
86+
- Not start with the package name
87+
- Not end with punctuation
8388

8489
### Ratchet checks
8590

nixpkgs-check.nix

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ let
4949
${initNix}
5050
# This is what nixpkgs-vet uses
5151
export NIXPKGS_VET_NIX_PACKAGE=${lib.getBin nix}
52-
${nixpkgs-vet}/bin/.nixpkgs-vet-wrapped --base "${nixpkgs}" "${nixpkgs}"
52+
time ${nixpkgs-vet}/bin/.nixpkgs-vet-wrapped --base "${nixpkgs}" "${nixpkgs}"
5353
touch $out
5454
'';
5555
in

src/eval.nix

+12-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,18 @@ let
5959
else
6060
{
6161
AttributeSet = {
62-
is_derivation = pkgs.lib.isDerivation value;
62+
derivation =
63+
if pkgs.lib.isDerivation value then
64+
{
65+
Derivation = {
66+
pname = pkgs.lib.getName value;
67+
description = value.meta.description or null;
68+
};
69+
}
70+
else
71+
{
72+
NonDerivation = null;
73+
};
6374
definition_variant =
6475
if !value ? _callPackageVariant then
6576
{ ManualDefinition.is_semantic_call_package = false; }

src/eval.rs

+117-7
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,22 @@ pub enum AttributeVariant {
7979
NonAttributeSet,
8080
AttributeSet {
8181
/// Whether the attribute is a derivation (`lib.isDerivation`)
82-
is_derivation: bool,
82+
derivation: Derivation,
8383
/// The type of `callPackage` used.
8484
definition_variant: DefinitionVariant,
8585
},
8686
}
8787

88+
/// Info about a derivation
89+
#[derive(Deserialize)]
90+
pub enum Derivation {
91+
NonDerivation,
92+
Derivation {
93+
pname: String,
94+
description: Option<String>,
95+
},
96+
}
97+
8898
#[derive(Deserialize)]
8999
pub enum DefinitionVariant {
90100
/// An automatic definition by the `pkgs/by-name` overlay, though it's detected using the
@@ -261,6 +271,61 @@ pub fn check_values(
261271
}))
262272
}
263273

274+
/// Check the validity of a package description
275+
fn check_description(pname: &str, description: &Option<String>) -> ratchet::PackageDescription {
276+
if let Some(text) = description {
277+
let lowercased = text.to_lowercase();
278+
ratchet::PackageDescription {
279+
not_capitalised: {
280+
if let Some(first) = text.chars().next() {
281+
if first.is_lowercase() {
282+
Loose(text.to_owned())
283+
} else {
284+
Tight
285+
}
286+
} else {
287+
Tight
288+
}
289+
},
290+
starts_with_article: {
291+
if lowercased.starts_with("a ")
292+
|| lowercased.starts_with("an ")
293+
|| lowercased.starts_with("the ")
294+
{
295+
Loose(text.to_owned())
296+
} else {
297+
Tight
298+
}
299+
},
300+
starts_with_package_name: {
301+
if lowercased.starts_with(&pname.to_lowercase()) {
302+
Loose(text.to_owned())
303+
} else {
304+
Tight
305+
}
306+
},
307+
ends_with_punctuation: {
308+
if let Some(last) = text.chars().last() {
309+
if last.is_ascii_punctuation() {
310+
Loose(text.to_owned())
311+
} else {
312+
Tight
313+
}
314+
} else {
315+
Tight
316+
}
317+
},
318+
}
319+
} else {
320+
ratchet::PackageDescription {
321+
not_capitalised: Tight,
322+
starts_with_article: Tight,
323+
starts_with_package_name: Tight,
324+
ends_with_punctuation: Tight,
325+
}
326+
}
327+
}
328+
264329
/// Handle the evaluation result for an attribute in `pkgs/by-name`, making it a validation result.
265330
fn by_name(
266331
nix_file_store: &mut NixFileStore,
@@ -279,6 +344,27 @@ fn by_name(
279344
.into()
280345
};
281346

347+
let description_ratchet = match by_name_attribute {
348+
Existing(AttributeInfo {
349+
attribute_variant:
350+
AttributeVariant::AttributeSet {
351+
derivation:
352+
Derivation::Derivation {
353+
pname: ref name,
354+
ref description,
355+
},
356+
..
357+
},
358+
..
359+
}) => check_description(name, description),
360+
_ => ratchet::PackageDescription {
361+
not_capitalised: NonApplicable,
362+
starts_with_article: NonApplicable,
363+
starts_with_package_name: NonApplicable,
364+
ends_with_punctuation: NonApplicable,
365+
},
366+
};
367+
282368
// At this point we know that `pkgs/by-name/fo/foo/package.nix` has to exists. This match
283369
// decides whether the attribute `foo` is defined accordingly and whether a legacy manual
284370
// definition could be removed.
@@ -308,16 +394,17 @@ fn by_name(
308394
// And it's an attribute set, which allows us to get more information about it
309395
attribute_variant:
310396
AttributeVariant::AttributeSet {
311-
is_derivation,
397+
derivation,
312398
definition_variant,
313399
},
314400
location,
315401
}) => {
316402
// Only derivations are allowed in `pkgs/by-name`.
317-
let is_derivation_result = if is_derivation {
318-
Success(())
319-
} else {
320-
to_validation(ByNameErrorKind::NonDerivation).map(|_| ())
403+
let is_derivation_result = match derivation {
404+
Derivation::Derivation { .. } => Success(()),
405+
Derivation::NonDerivation => {
406+
to_validation(ByNameErrorKind::NonDerivation).map(|_| ())
407+
}
321408
};
322409

323410
// If the definition looks correct
@@ -399,6 +486,7 @@ fn by_name(
399486
// once at the end with a map.
400487
manual_definition_result.map(|manual_definition| ratchet::Package {
401488
manual_definition,
489+
description: description_ratchet,
402490
uses_by_name: Tight,
403491
}),
404492
)
@@ -477,6 +565,27 @@ fn handle_non_by_name_attribute(
477565
use ratchet::RatchetState::*;
478566
use NonByNameAttribute::*;
479567

568+
let description_ratchet = match non_by_name_attribute {
569+
EvalSuccess(AttributeInfo {
570+
attribute_variant:
571+
AttributeVariant::AttributeSet {
572+
derivation:
573+
Derivation::Derivation {
574+
pname: ref name,
575+
ref description,
576+
},
577+
..
578+
},
579+
..
580+
}) => check_description(name, description),
581+
_ => ratchet::PackageDescription {
582+
not_capitalised: NonApplicable,
583+
starts_with_article: NonApplicable,
584+
starts_with_package_name: NonApplicable,
585+
ends_with_punctuation: NonApplicable,
586+
},
587+
};
588+
480589
// The ratchet state whether this attribute uses `pkgs/by-name`.
481590
//
482591
// This is never `Tight`, because we only either:
@@ -504,7 +613,7 @@ fn handle_non_by_name_attribute(
504613
// are. Anything else can't be in `pkgs/by-name`.
505614
attribute_variant: AttributeVariant::AttributeSet {
506615
// As of today, non-derivation attribute sets can't be in `pkgs/by-name`.
507-
is_derivation: true,
616+
derivation: Derivation::Derivation { .. },
508617
// Of the two definition variants, really only the manual one makes sense here.
509618
//
510619
// Special cases are:
@@ -601,5 +710,6 @@ fn handle_non_by_name_attribute(
601710
// ourselves all the time to define `manual_definition`, just set it once at the end here.
602711
manual_definition: Tight,
603712
uses_by_name,
713+
description: description_ratchet,
604714
}))
605715
}

src/nixpkgs_problem.rs

+27
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub enum NixpkgsProblem {
1919
NixFile(NixFileError),
2020
TopLevelPackage(TopLevelPackageError),
2121
NixEval(NixEvalError),
22+
Description(DescriptionError),
2223
}
2324

2425
/// A file structure error involving a shard (e.g. `fo` is the shard in the path `pkgs/by-name/fo/foo/package.nix`)
@@ -146,6 +147,23 @@ pub struct NixEvalError {
146147
pub stderr: String,
147148
}
148149

150+
/// An error relating to the description of a package
151+
#[derive(Clone)]
152+
pub struct DescriptionError {
153+
pub package_name: String,
154+
pub description: String,
155+
pub kind: DescriptionErrorKind,
156+
}
157+
158+
/// The kind of description problem
159+
#[derive(Clone)]
160+
pub enum DescriptionErrorKind {
161+
NotCapitalised,
162+
StartsWithArticle,
163+
StartsWithPackageName,
164+
EndsWithPunctuation,
165+
}
166+
149167
impl fmt::Display for NixpkgsProblem {
150168
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151169
match self {
@@ -440,6 +458,15 @@ impl fmt::Display for NixpkgsProblem {
440458
f.write_str(stderr)?;
441459
write!(f, "- Nix evaluation failed for some package in `pkgs/by-name`, see error above")
442460
},
461+
NixpkgsProblem::Description(DescriptionError { package_name, description, kind }) => {
462+
let text = match kind {
463+
DescriptionErrorKind::NotCapitalised => "is not capitalised",
464+
DescriptionErrorKind::StartsWithArticle => "starts with an article (the/a/an)",
465+
DescriptionErrorKind::StartsWithPackageName => "starts with the package name",
466+
DescriptionErrorKind::EndsWithPunctuation => "ends with punctuation",
467+
};
468+
write!(f, "- Description for package {package_name} (\"{description}\") {text}")
469+
},
443470
}
444471
}
445472
}

0 commit comments

Comments
 (0)