Skip to content

Commit 20d3a92

Browse files
authored
Merge pull request #7 from gherlint/refactor
Move lint functions to rules
2 parents 43f1d5c + c9a2392 commit 20d3a92

File tree

7 files changed

+136
-125
lines changed

7 files changed

+136
-125
lines changed

server/src/linter/index.js

Lines changed: 6 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,10 @@
1-
const { DiagnosticSeverity: Severity } = require('vscode-languageserver/node');
2-
const { replaceCommentsTags, replaceDocString, replaceStory, getLineKeyword } = require('../utils');
3-
const Keywords = require('../keywords');
4-
const { addToDiagnostics } = require('./helper');
5-
const Messages = require('./messages');
6-
const { globalMatch, firstMatch } = require('../regex');
1+
const rules = require('./rules');
72

8-
module.exports.validateDocument = function (document, docConfig) {
3+
module.exports.lint = function (document, docConfig) {
94
const diagnostics = [];
10-
validateFeatureOccurance(document, diagnostics);
11-
validateStartingStep(document, diagnostics);
12-
13-
const simpleText = getSimpleText(document);
14-
validateByLine(document, simpleText, diagnostics);
15-
return diagnostics;
16-
};
17-
18-
function validateFeatureOccurance(document, diagnostics) {
195
const text = document.getText();
20-
const regex = globalMatch.feature;
21-
let matchCount = 0;
22-
while ((match = regex.exec(text))) {
23-
matchCount++;
24-
// only show error from second match onward
25-
if (matchCount > 1) {
26-
addToDiagnostics(
27-
document,
28-
diagnostics,
29-
match.index,
30-
match.index + match[0].length,
31-
Messages.mustHaveOnlyOneFeature,
32-
Severity.Error
33-
);
34-
}
35-
}
36-
if (!matchCount) {
37-
addToDiagnostics(document, diagnostics, 1, 1, Messages.mustHaveFeatureName, Severity.Error);
38-
}
39-
}
40-
41-
function validateStartingStep(document, diagnostics) {
42-
const text = replaceCommentsTags(document.getText());
43-
const regex = globalMatch.beginningStep;
44-
45-
while ((match = regex.exec(text))) {
46-
matchStep = match[0].trim();
47-
if (![Keywords.Given, Keywords.When].includes(matchStep)) {
48-
addToDiagnostics(
49-
document,
50-
diagnostics,
51-
match.index,
52-
match.index + match[0].length,
53-
Messages.firstStepShouldBeGivenOrWhen
54-
);
55-
}
56-
}
57-
}
58-
59-
function validateByLine(document, text, diagnostics) {
60-
const lines = text.split('\n');
61-
const regex = firstMatch.step;
62-
63-
let prevStep = '';
64-
let index = 0;
65-
let keywordHit = false;
66-
lines.forEach((line) => {
67-
let lineLength = line.length;
68-
if (lineLength === 0) {
69-
lineLength = 1;
70-
} else {
71-
// also count line break
72-
lineLength += 1;
73-
}
74-
75-
// do not process empty line
76-
if (!line.trim().length) {
77-
index += lineLength;
78-
return;
79-
}
80-
81-
const keyword = getLineKeyword(line);
82-
if (!keyword) {
83-
addToDiagnostics(document, diagnostics, index, index + line.length, Messages.invalidLine, Severity.Error);
84-
index += lineLength;
85-
return;
86-
} else if (!keywordHit && keyword !== Keywords.Feature) {
87-
addToDiagnostics(
88-
document,
89-
diagnostics,
90-
index,
91-
index + line.length,
92-
Messages.mustStartWithFeatureName,
93-
Severity.Error
94-
);
95-
}
96-
keywordHit = true;
97-
98-
const match = regex.exec(line);
99-
if (Boolean(match)) {
100-
const matchStep = match[0];
101-
if (prevStep === matchStep) {
102-
const rangeEnd = index + match.index + matchStep.length;
103-
let message = Messages.repeatedStep;
104-
if (matchStep === Keywords.Then) {
105-
message = Messages.repeatedStepForThen;
106-
}
107-
addToDiagnostics(document, diagnostics, index + match.index, rangeEnd, message);
108-
}
109-
if (matchStep !== Keywords.And) {
110-
prevStep = matchStep;
111-
}
112-
} else {
113-
prevStep = '';
114-
}
115-
index += lineLength;
6+
Object.keys(rules).forEach(function (ruleId) {
7+
rules[ruleId].run(document, text, diagnostics);
1168
});
117-
}
118-
119-
// returns text where tags, comments, user story and docstring are replaced with spaces
120-
function getSimpleText(document) {
121-
let text = replaceCommentsTags(document.getText());
122-
text = replaceDocString(text);
123-
text = replaceStory(text);
124-
return text;
125-
}
9+
return diagnostics;
10+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const { globalMatch } = require('../../regex');
2+
const Keywords = require('../../keywords');
3+
const Messages = require('../messages');
4+
const { addToDiagnostics } = require('../helper');
5+
6+
module.exports = {
7+
run: function (document, text, diagnostics) {
8+
const regex = globalMatch.beginningStep;
9+
10+
while ((match = regex.exec(text))) {
11+
if (![Keywords.Given, Keywords.When].includes(match[0])) {
12+
addToDiagnostics(
13+
document,
14+
diagnostics,
15+
match.index,
16+
match.index + match[0].length,
17+
Messages.firstStepShouldBeGivenOrWhen
18+
);
19+
}
20+
}
21+
},
22+
};

server/src/linter/rules/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
only_one_feature: require('./only_one_feature'),
3+
first_step: require('./first_step'),
4+
repetitive_step: require('./repetitive_step'),
5+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const { DiagnosticSeverity: Severity } = require('vscode-languageserver/node');
2+
const { globalMatch } = require('../../regex');
3+
const Messages = require('../messages');
4+
const { addToDiagnostics } = require('../helper');
5+
6+
module.exports = {
7+
run: function (document, text, diagnostics) {
8+
const regex = globalMatch.feature;
9+
let matchCount = 0;
10+
11+
while ((match = regex.exec(text))) {
12+
matchCount++;
13+
// only show error from second match onward
14+
if (matchCount > 1) {
15+
addToDiagnostics(
16+
document,
17+
diagnostics,
18+
match.index,
19+
match.index + match[0].length,
20+
Messages.mustHaveOnlyOneFeature,
21+
Severity.Error
22+
);
23+
}
24+
}
25+
if (!matchCount) {
26+
addToDiagnostics(document, diagnostics, 1, 1, Messages.mustHaveFeatureName, Severity.Error);
27+
}
28+
},
29+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const { firstMatch } = require('../../regex');
2+
const Keywords = require('../../keywords');
3+
const Messages = require('../messages');
4+
const { addToDiagnostics } = require('../helper');
5+
const { replaceCommentsTags } = require('../../utils');
6+
7+
function isStepKeyword(keyword) {
8+
return [Keywords.Given, Keywords.When, Keywords.Then, Keywords.But, Keywords.And].includes(keyword);
9+
}
10+
11+
function isScenarioKeyword(keyword) {
12+
return [
13+
Keywords.Background,
14+
Keywords.Scenario,
15+
Keywords.Example,
16+
Keywords['Scenario Template'],
17+
Keywords['Scenario Outline'],
18+
].includes(keyword);
19+
}
20+
21+
module.exports = {
22+
run: function (document, text, diagnostics) {
23+
const sanitizedText = replaceCommentsTags(text);
24+
const lines = sanitizedText.split('\n');
25+
const regex = firstMatch.keyword;
26+
27+
let prevStep = '';
28+
let position = 0;
29+
let enteredScenario = false;
30+
31+
lines.forEach((line) => {
32+
let lineLength = line.length;
33+
if (lineLength === 0) {
34+
lineLength = 1;
35+
} else {
36+
// also count line break
37+
lineLength += 1;
38+
}
39+
40+
// do not process empty line
41+
if (!line.trim().length) {
42+
position += lineLength;
43+
return;
44+
}
45+
46+
const match = regex.exec(line);
47+
if (Boolean(match)) {
48+
const keyword = match[0];
49+
if (isStepKeyword(keyword)) {
50+
enteredScenario = true;
51+
if (prevStep === keyword) {
52+
if (keyword !== Keywords.And) {
53+
const rangeEnd = position + match.index + keyword.length;
54+
let message = Messages.repeatedStep;
55+
if (keyword === Keywords.Then) {
56+
message = Messages.repeatedStepForThen;
57+
}
58+
addToDiagnostics(document, diagnostics, position + match.index, rangeEnd, message);
59+
}
60+
}
61+
prevStep = keyword;
62+
} else if (enteredScenario && isScenarioKeyword(keyword)) {
63+
prevStep = '';
64+
enteredScenario = false;
65+
}
66+
}
67+
position += lineLength;
68+
});
69+
},
70+
};

server/src/regex.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = {
22
globalMatch: {
3-
feature: /(?<!\S( )*)Feature:/g,
3+
feature: /(?<!\S)Feature:/g,
44
beginningStep:
55
/(?<=(Background:|Scenario( (Outline|Template))?:|Example:)(.*)[\n\r](\s)*)(Given|When|Then|And|But)(?= )/g,
66
docString: /"""[\s\S]*?"""/g,

server/src/server.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const {
88
const { TextDocument } = require('vscode-languageserver-textdocument');
99

1010
const defaultSettings = require('./defaultSettings');
11-
const { validateDocument } = require('./linter');
11+
const { lint } = require('./linter');
1212

1313
// Create a connection for the server, using Node's IPC as a transport.
1414
// Also include all preview / proposed LSP features.
@@ -73,7 +73,7 @@ connection.onDidChangeConfiguration((change) => {
7373
// Revalidate all open documents
7474
documents.all().forEach((document) => {
7575
const docConfig = getDocumentConfig(document.uri);
76-
validateDocument(document, docConfig);
76+
lint(document, docConfig);
7777
});
7878
});
7979

@@ -87,7 +87,7 @@ documents.onDidClose((_event) => {
8787
documents.onDidChangeContent(async ({ document }) => {
8888
const docConfig = await getDocumentConfig(document.uri);
8989
// Revalidate the document
90-
const diagnostics = validateDocument(document, docConfig);
90+
const diagnostics = lint(document, docConfig);
9191

9292
// Send the computed diagnostics to VS Code.
9393
connection.sendDiagnostics({ uri: document.uri, diagnostics });

0 commit comments

Comments
 (0)