Repository: sindresorhus/awesome-lint
Pull request #83 (2019-05-27 13:49:33)

Improve awesome/git-repo-age error message
The "Awesome list must reside in a valid git repository" error message
(awesome/git-repo-age) also appears when (e.g. Travis) makes a shallow
copy of the awesome repo.  This is documented in the README file, but
users may miss it.  Rephrase error message and add a pointer to the
README file.


Patch:
diff --git a/rules/git-repo-age.js b/rules/git-repo-age.js
index cbf5c67..dceba88 100644
--- a/rules/git-repo-age.js
+++ b/rules/git-repo-age.js
@@ -35,8 +35,8 @@ module.exports = rule('remark-lint:awesome/git-repo-age', async (ast, file) => {
 			file.message(`Git repository must be at least ${minGitRepoAgeDays} days old`);
 		}
 	} catch (_) {
-		// Most likely not a Git repository
-		file.message('Awesome list must reside in a valid git repository');
+		// Not a Git repository or a shallow copy missing the `.travis.yml` `git:\ndepth: false` setting
+		file.message('Awesome list must reside in a valid deep-cloned Git repository (see https://github.com/sindresorhus/awesome-lint#tip for more information)');
 	}
 });
 
diff --git a/rules/heading.js b/rules/heading.js
new file mode 100644
index 0000000..9873670
--- /dev/null
+++ b/rules/heading.js
@@ -0,0 +1,49 @@
+'use strict';
+const rule = require('unified-lint-rule');
+const visit = require('unist-util-visit');
+const {of: caseOf, title: titleCase} = require('case');
+
+const listHeadingCaseWhitelist = new Set([
+	'title',
+	'capital'
+]);
+
+module.exports = rule('remark-lint:awesome/heading', (ast, file) => {
+	let headings = 0;
+
+	visit(ast, (node, index) => {
+		if (node.type !== 'heading') {
+			return;
+		}
+
+		if (node.depth > 1) {
+			if (index !== 0) {
+				return;
+			}
+
+			file.message('Main list heading must be of depth 1', node);
+		}
+
+		for (const child of node.children) {
+			if (child.type !== 'text') {
+				continue;
+			}
+
+			const headingText = child.value;
+
+			if (!listHeadingCaseWhitelist.has(caseOf(headingText)) && titleCase(headingText) !== headingText) {
+				file.message('Main heading must be in title case', node);
+			}
+		}
+
+		headings++;
+
+		if (headings > 1) {
+			file.message('List can only have one heading', node);
+		}
+	});
+
+	if (headings === 0) {
+		file.message('Missing main list heading');
+	}
+});
diff --git a/rules/index.js b/rules/index.js
index e18a3fc..563fa9f 100644
--- a/rules/index.js
+++ b/rules/index.js
@@ -1,6 +1,7 @@
 'use strict';
 
 module.exports = [
+	require('./heading'),
 	require('./badge'),
 	require('./contributing'),
 	require('./git-repo-age'),


Test patch:
diff --git a/test/fixtures/heading/error0.md b/test/fixtures/heading/error0.md
new file mode 100644
index 0000000..194ad63
--- /dev/null
+++ b/test/fixtures/heading/error0.md
@@ -0,0 +1,1 @@
+> This is an awesome list!
diff --git a/test/fixtures/heading/error1.md b/test/fixtures/heading/error1.md
new file mode 100644
index 0000000..6fd6116
--- /dev/null
+++ b/test/fixtures/heading/error1.md
@@ -0,0 +1,3 @@
+# my-awesome-list
+
+> This is an awesome list!
diff --git a/test/fixtures/heading/error2.md b/test/fixtures/heading/error2.md
new file mode 100644
index 0000000..021fb06
--- /dev/null
+++ b/test/fixtures/heading/error2.md
@@ -0,0 +1,3 @@
+# Awesome List
+
+# This Is a Really Awesome List
diff --git a/test/fixtures/heading/error3.md b/test/fixtures/heading/error3.md
new file mode 100644
index 0000000..c8ccf41
--- /dev/null
+++ b/test/fixtures/heading/error3.md
@@ -0,0 +1,1 @@
+## Improper Awesome List
diff --git a/test/fixtures/heading/success0.md b/test/fixtures/heading/success0.md
new file mode 100644
index 0000000..afd9d05
--- /dev/null
+++ b/test/fixtures/heading/success0.md
@@ -0,0 +1,3 @@
+# My Awesome List
+
+> This is an awesome list
diff --git a/test/fixtures/heading/success1.md b/test/fixtures/heading/success1.md
new file mode 100644
index 0000000..268fa05
--- /dev/null
+++ b/test/fixtures/heading/success1.md
@@ -0,0 +1,1 @@
+# AHA Acronyms Are Allowed
diff --git a/test/rules/git-repo-age.js b/test/rules/git-repo-age.js
index 13d4672..86592e1 100644
--- a/test/rules/git-repo-age.js
+++ b/test/rules/git-repo-age.js
@@ -30,7 +30,7 @@ test.serial('git-repo-age - error invalid git repo', async t => {
 	t.deepEqual(messages, [
 		{
 			ruleId: 'awesome/git-repo-age',
-			message: 'Awesome list must reside in a valid git repository'
+			message: 'Awesome list must reside in a valid deep-cloned Git repository (see https://github.com/sindresorhus/awesome-lint#tip for more information)'
 		}
 	]);
 });
diff --git a/test/rules/heading.js b/test/rules/heading.js
new file mode 100644
index 0000000..03483ae
--- /dev/null
+++ b/test/rules/heading.js
@@ -0,0 +1,59 @@
+import test from 'ava';
+import lint from '../_lint';
+
+const config = {
+	plugins: [
+		require('remark-lint'),
+		require('../../rules/heading')
+	]
+};
+
+test('heading - missing', async t => {
+	const messages = await lint({config, filename: 'test/fixtures/heading/error0.md'});
+	t.deepEqual(messages, [
+		{
+			ruleId: 'awesome/heading',
+			message: 'Missing main list heading'
+		}
+	]);
+});
+
+test('heading - not in title case', async t => {
+	const messages = await lint({config, filename: 'test/fixtures/heading/error1.md'});
+	t.deepEqual(messages, [
+		{
+			ruleId: 'awesome/heading',
+			message: 'Main heading must be in title case'
+		}
+	]);
+});
+
+test('heading - more than one heading', async t => {
+	const messages = await lint({config, filename: 'test/fixtures/heading/error2.md'});
+	t.deepEqual(messages, [
+		{
+			ruleId: 'awesome/heading',
+			message: 'List can only have one heading'
+		}
+	]);
+});
+
+test('heading - depth is bigger than 1', async t => {
+	const messages = await lint({config, filename: 'test/fixtures/heading/error3.md'});
+	t.deepEqual(messages, [
+		{
+			ruleId: 'awesome/heading',
+			message: 'Main list heading must be of depth 1'
+		}
+	]);
+});
+
+test('heading - success', async t => {
+	const messages = await lint({config, filename: 'test/fixtures/heading/success0.md'});
+	t.deepEqual(messages, []);
+});
+
+test('heading - success (with acronyms)', async t => {
+	const messages = await lint({config, filename: 'test/fixtures/heading/success1.md'});
+	t.deepEqual(messages, []);
+});