Skip to content

Commit d139254

Browse files
authored
Merge pull request #257 from InnerSourceCommons/linter-for-patterns-new
Custom linter to check the format of the patterns
2 parents 193fb17 + 084d661 commit d139254

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-0
lines changed

.github/workflows/lint-patterns.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# from: https://github.com/marketplace/actions/markdown-linting-action
2+
name: Pattern Syntax Validation
3+
4+
on:
5+
push:
6+
branches:
7+
- "master"
8+
pull_request:
9+
10+
jobs:
11+
validate:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v2
15+
- name: Problem Matcher for markdownlint-cli
16+
uses: xt0rted/markdownlint-problem-matcher@v1
17+
- name: Lint pattern files (markdown)
18+
uses: avto-dev/markdown-lint@v1
19+
with:
20+
rules: './lint/pattern-template.js'
21+
config: './lint/pattern-template.yml'
22+
args: 'patterns/2-structured/*.md patterns/2-structured/project-setup/*.md patterns/3-validated/*.md'

lint/pattern-template.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"use strict";
2+
3+
module.exports = [
4+
{
5+
names: ["PATTERN-TEMPLATE-RULE-001"],
6+
description: "h2 headlines and below",
7+
tags: ["headings", "headers", "pattern-template"],
8+
function: (params, onError) => {
9+
params.tokens.filter(function filterToken(token) {
10+
return token.type === "heading_open";
11+
}).forEach(function forToken(token) {
12+
if (token.tag === "h1") {
13+
return onError({
14+
lineNumber: token.lineNumber,
15+
detail: "Use of #-headlines (h1) is not allowed in patterns. Please only use ##-headlines (h2) and lower.",
16+
context: token.line
17+
});
18+
}
19+
});
20+
}
21+
},
22+
{
23+
names: ["PATTERN-TEMPLATE-RULE-002"],
24+
description: "Standard Headlines",
25+
tags: ["headings", "headers", "pattern-template"],
26+
function: (params, onError) => {
27+
var allowedHeadlines = "Title|Patlet|Problem|Story|Context|Forces|Solutions|Resulting Context|Known Instances|Status|Author(s)|Acknowledgements";
28+
var re = new RegExp(`^## (${allowedHeadlines.replace(/(?=[\(\)])/g, '\\')})\\s*$`,"m");
29+
// console.log(re);
30+
31+
params.tokens.filter(function filterToken(token) {
32+
return token.type === "heading_open";
33+
}).forEach(function forToken(token) {
34+
if (token.tag === "h2") {
35+
if (re.test(token.line)) {
36+
return;
37+
}
38+
// if (/^## ()$/m.test(token.line)) {
39+
// return;
40+
// }
41+
42+
return onError({
43+
lineNumber: token.lineNumber,
44+
detail: "Allowed types are: " + allowedHeadlines.replace(/\|/g, ', '),
45+
context: token.line
46+
});
47+
}
48+
});
49+
}
50+
},
51+
{
52+
names: ["PATTERN-TEMPLATE-RULE-003"],
53+
description: "Mandatory template sections",
54+
tags: ["headings", "headers", "pattern-template"],
55+
function: (params, onError) => {
56+
var mandatoryHeadlines = {
57+
"Title": "Title",
58+
"Patlet": "Patlet",
59+
"Problem": "Problem",
60+
"Context": "Context",
61+
"Forces": "Forces",
62+
"Solutions": "Solution|Solutions",
63+
"Resulting Context": "Resulting Context",
64+
"Known Instances": "Known Instances",
65+
"Status": "Status",
66+
"Author(s)": "Author|Authors|(Author\\(s\\))"
67+
};
68+
69+
var collectedHeadlines = ""
70+
71+
// collect all h2 headlines
72+
// (only the headline text itself, removing markdown sytnax and whitespace)
73+
params.tokens.filter(function filterToken(token) {
74+
return token.type === "heading_open";
75+
}).forEach(function forToken(token) {
76+
if (token.tag === "h2") {
77+
let re = new RegExp("^## (.*?)\\s*$","m");
78+
let matchResult = token.line.match(re);
79+
80+
if (matchResult != null) {
81+
collectedHeadlines += matchResult[1] + "\n"
82+
}
83+
}
84+
});
85+
86+
// confirm if all `mandatoryHeadlines` exist exactly once in the `collectedHeadlines`
87+
var errorsFound = [];
88+
89+
let headline;
90+
for (headline in mandatoryHeadlines){
91+
let pattern = mandatoryHeadlines[headline];
92+
let re = new RegExp(`^${pattern}$`,"gm");
93+
let matchResult = collectedHeadlines.match(re);
94+
95+
// TODO as soon as I use the option 'g' in the regexp, it does not return a full match array anymore
96+
if (matchResult != null) {
97+
if (matchResult.length >= 2 ) {
98+
errorsFound.push(`Duplicate headline "${headline}".`);
99+
}
100+
}
101+
else {
102+
errorsFound.push(`Required headline "${headline}" is missing.`);
103+
}
104+
}
105+
106+
// if any errors were found, raise a linter error
107+
if (errorsFound.length > 0) {
108+
return onError({
109+
lineNumber: 1,
110+
detail: errorsFound.join(" ")
111+
});
112+
}
113+
}
114+
}
115+
];

lint/pattern-template.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
default: false # excludes all default markdownlint rules
2+
3+
# Custom rules:
4+
PATTERN-TEMPLATE-RULE-001: true
5+
# PATTERN-TEMPLATE-RULE-002: true # will likely stop using this in favor of rule 003
6+
PATTERN-TEMPLATE-RULE-003: true

0 commit comments

Comments
 (0)