diff --git a/cmd/lint.go b/cmd/lint.go index 1ee5056..dc0d2aa 100644 --- a/cmd/lint.go +++ b/cmd/lint.go @@ -89,6 +89,10 @@ func lintTrack(path string) bool { check: missingMetadata, msg: "An implementation for '%v' was found, but config.json does not reference this exercise.", }, + { + check: missingReadme, + msg: "The implementation for '%v' is missing a README.", + }, { check: missingSolution, msg: "The implementation for '%v' is missing an example solution.", @@ -201,6 +205,25 @@ func missingSolution(t track.Track) []string { return slugs } +func missingReadme(t track.Track) []string { + readmes := map[string]bool{} + for _, exercise := range t.Exercises { + readmes[exercise.Slug] = exercise.HasReadme() + } + // Don't complain about missing readmes in foregone exercises. + for _, slug := range t.Config.ForegoneSlugs { + readmes[slug] = true + } + + slugs := []string{} + for slug, ok := range readmes { + if !ok { + slugs = append(slugs, slug) + } + } + return slugs +} + func missingTestSuite(t track.Track) []string { tests := map[string]bool{} for _, exercise := range t.Exercises { diff --git a/cmd/lint_test.go b/cmd/lint_test.go index 44ee1ae..9fced1f 100644 --- a/cmd/lint_test.go +++ b/cmd/lint_test.go @@ -45,6 +45,11 @@ func TestLintTrack(t *testing.T) { path: "../fixtures/broken-maintainers", expected: true, }, + { + desc: "should fail when given a track missing READMEs.", + path: "../fixtures/missing-readme", + expected: true, + }, { desc: "should not fail when given a track with all of its bits in place.", path: "../fixtures/lint/valid-track", @@ -110,6 +115,27 @@ func TestMissingMetadata(t *testing.T) { assert.Equal(t, "cherry", slugs[1]) } +func TestMissingReadme(t *testing.T) { + track := track.Track{ + Exercises: []track.Exercise{ + {Slug: "apple"}, + {Slug: "banana", ReadmePath: "README.md"}, + {Slug: "cherry"}, + }, + } + + slugs := missingReadme(track) + + if len(slugs) != 2 { + t.Fatalf("Expected missing READMEs in 2 exercises, missing in %d", len(slugs)) + } + + sort.Strings(slugs) + + assert.Equal(t, "apple", slugs[0]) + assert.Equal(t, "cherry", slugs[1]) +} + func TestMissingSolution(t *testing.T) { track := track.Track{ Exercises: []track.Exercise{ diff --git a/fixtures/lint/valid-track/exercises/aluminum/README.md b/fixtures/lint/valid-track/exercises/aluminum/README.md new file mode 100644 index 0000000..e69de29 diff --git a/fixtures/missing-readme/config.json b/fixtures/missing-readme/config.json new file mode 100644 index 0000000..352a99b --- /dev/null +++ b/fixtures/missing-readme/config.json @@ -0,0 +1,14 @@ +{ + "slug": "missing-readme", + "language": "Missing Readme", + "repository": "https://github.com/exercism/missing-readme", + "active": true, + "exercises": [ + { + "uuid": "missingmissing", + "slug": "missing-readme", + "topics": [], + "difficulty": 1 + } + ] +} diff --git a/fixtures/missing-readme/exercises/missing-readme/example.ext b/fixtures/missing-readme/exercises/missing-readme/example.ext new file mode 100644 index 0000000..e69de29 diff --git a/fixtures/missing-readme/exercises/missing-readme/test.ext b/fixtures/missing-readme/exercises/missing-readme/test.ext new file mode 100644 index 0000000..e69de29 diff --git a/fixtures/numbers/exercises/one/README.md b/fixtures/numbers/exercises/one/README.md new file mode 100644 index 0000000..e69de29 diff --git a/fixtures/numbers/exercises/three/README.md b/fixtures/numbers/exercises/three/README.md new file mode 100644 index 0000000..e69de29 diff --git a/fixtures/numbers/exercises/two/README.md b/fixtures/numbers/exercises/two/README.md new file mode 100644 index 0000000..e69de29 diff --git a/track/exercise.go b/track/exercise.go index 944223d..aac51f8 100644 --- a/track/exercise.go +++ b/track/exercise.go @@ -11,6 +11,7 @@ import ( // Exercise is an implementation of an Exercism exercise. type Exercise struct { Slug string + ReadmePath string SolutionPath string TestSuitePath string } @@ -31,6 +32,11 @@ func NewExercise(root string, pg PatternGroup) (Exercise, error) { return ex, err } + err = setPath(root, "README\\.md", &ex.ReadmePath) + if err != nil { + return ex, err + } + return ex, err } @@ -62,6 +68,11 @@ func setPath(root, pattern string, field *string) error { return filepath.Walk(root, walkFn) } +// HasReadme checks that an exercise has a README. +func (ex Exercise) HasReadme() bool { + return ex.ReadmePath != "" +} + // HasTestSuite checks that an exercise has a test suite. func (ex Exercise) HasTestSuite() bool { return ex.TestSuitePath != ""