Skip to content

Commit b4d45cc

Browse files
committed
Add local diff test script
1 parent d23e70e commit b4d45cc

File tree

1 file changed

+275
-0
lines changed

1 file changed

+275
-0
lines changed
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
#!/usr/bin/env node
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
6+
// Dynamic path resolution to work from any directory
7+
const scriptDir = __dirname;
8+
const gitHubDiffParserPath = path.join(scriptDir, 'github-diff-parser');
9+
const validateGitHubPath = path.join(scriptDir, 'validate-content-style-github');
10+
const ruleParserPath = path.join(scriptDir, 'rule-parser');
11+
const styleRulesPath = path.join(scriptDir, 'style-rules.yml');
12+
13+
const GitHubURLDiffParser = require(gitHubDiffParserPath);
14+
const GitHubDocumentationValidator = require(validateGitHubPath);
15+
16+
/**
17+
* Local test script for validating documentation with a diff file
18+
* Usage: node test-local-diff.js path/to/diff-file.patch [PR_NUMBER]
19+
*/
20+
class LocalDiffTester {
21+
constructor(diffFilePath, prNumber = null) {
22+
this.diffFilePath = diffFilePath;
23+
this.prNumber = prNumber;
24+
this.diffParser = new GitHubURLDiffParser({ verbose: true });
25+
}
26+
27+
async testValidation() {
28+
try {
29+
console.log('🎯 Local Diff Validation Test');
30+
console.log('============================');
31+
console.log(`📄 Diff file: ${this.diffFilePath}`);
32+
if (this.prNumber) {
33+
console.log(`🔍 PR number: #${this.prNumber}`);
34+
}
35+
console.log('');
36+
37+
// Read the diff file
38+
if (!fs.existsSync(this.diffFilePath)) {
39+
throw new Error(`Diff file not found: ${this.diffFilePath}`);
40+
}
41+
42+
const diffContent = fs.readFileSync(this.diffFilePath, 'utf8');
43+
console.log(`✅ Diff file loaded (${diffContent.length} characters)`);
44+
45+
// Parse the diff
46+
const parsedDiff = this.diffParser.parsePRDiff(diffContent);
47+
console.log(`📊 Found ${parsedDiff.totalFiles} markdown files in diff`);
48+
49+
if (parsedDiff.totalFiles === 0) {
50+
console.log('📝 No markdown files found in diff');
51+
return;
52+
}
53+
54+
// Display files found
55+
console.log('\n📋 Files to validate:');
56+
Object.keys(parsedDiff.files).forEach(filePath => {
57+
const info = parsedDiff.files[filePath];
58+
if (info.isNewFile) {
59+
console.log(` 📄 ${filePath} (new file)`);
60+
} else if (info.isDeletedFile) {
61+
console.log(` 🗑️ ${filePath} (deleted)`);
62+
} else {
63+
console.log(` 📝 ${filePath} (${info.addedLines.size} added, ${info.deletedLines.size} deleted lines)`);
64+
}
65+
});
66+
67+
// Create a mock validator to test our logic
68+
const validator = new MockGitHubValidator({
69+
prNumber: this.prNumber,
70+
verbose: true
71+
});
72+
73+
// Process each file
74+
let totalIssues = 0;
75+
for (const [filePath, diffInfo] of Object.entries(parsedDiff.files)) {
76+
console.log(`\n🔍 Validating: ${filePath}`);
77+
78+
const issues = await validator.validateFileWithDiff(filePath, diffInfo);
79+
totalIssues += issues.length;
80+
81+
if (issues.length === 0) {
82+
console.log(` ✅ No issues found`);
83+
} else {
84+
console.log(` ⚠️ ${issues.length} issues found:`);
85+
issues.forEach(issue => {
86+
console.log(` - Line ${issue.line}: ${issue.message}`);
87+
});
88+
}
89+
}
90+
91+
// Generate mock GitHub comment
92+
console.log('\n📝 Mock GitHub Comment:');
93+
console.log('========================');
94+
const mockComment = this.generateMockGitHubComment(parsedDiff, totalIssues);
95+
console.log(mockComment);
96+
97+
// Save results to file
98+
const results = {
99+
timestamp: new Date().toISOString(),
100+
prNumber: this.prNumber,
101+
mode: 'local-diff-test',
102+
summary: {
103+
filesProcessed: parsedDiff.totalFiles,
104+
totalIssues: totalIssues
105+
},
106+
diffFile: this.diffFilePath
107+
};
108+
109+
fs.writeFileSync('local-test-results.json', JSON.stringify(results, null, 2));
110+
console.log('\n💾 Results saved to: local-test-results.json');
111+
112+
} catch (error) {
113+
console.error('❌ Test failed:', error.message);
114+
process.exit(1);
115+
}
116+
}
117+
118+
generateMockGitHubComment(parsedDiff, totalIssues) {
119+
let comment = `## 🎯 Strapi Documentation Style Review (LOCAL TEST)\n\n`;
120+
comment += `*Automated analysis using Strapi's 12 Rules of Technical Writing*\n\n`;
121+
comment += `**🚀 Test Mode:** Local diff file analysis\n`;
122+
comment += `**📊 Source:** ${this.diffFilePath}\n`;
123+
if (this.prNumber) {
124+
comment += `**🎯 PR:** #${this.prNumber}\n`;
125+
}
126+
comment += '\n';
127+
128+
comment += '### 📊 Analysis Results\n';
129+
comment += `- **Files analyzed:** ${parsedDiff.totalFiles}\n`;
130+
comment += `- **Total issues:** ${totalIssues}\n\n`;
131+
132+
if (totalIssues === 0) {
133+
comment += '🎉 **Perfect!** Your documentation changes follow all 12 technical writing rules.\n\n';
134+
} else {
135+
comment += `⚠️ Found ${totalIssues} issues that should be addressed.\n\n`;
136+
}
137+
138+
comment += '**📚 Resources:**\n';
139+
comment += '- [Strapi\'s 12 Rules of Technical Writing](https://strapi.notion.site/12-Rules-of-Technical-Writing-c75e080e6b19432287b3dd61c2c9fa04)\n';
140+
comment += '- [Documentation Style Guide](https://github.com/strapi/documentation/blob/main/STYLE_GUIDE.pdf)\n\n';
141+
comment += '*🧪 This is a local test simulation of what would be posted on GitHub.*\n';
142+
143+
return comment;
144+
}
145+
}
146+
147+
// Mock validator for local testing
148+
class MockGitHubValidator {
149+
constructor(options) {
150+
this.options = options;
151+
// Import the real rule parser with correct path
152+
const Strapi12RulesParser = require(ruleParserPath);
153+
this.ruleParser = new Strapi12RulesParser(styleRulesPath);
154+
this.diffParser = new GitHubURLDiffParser({ verbose: options.verbose });
155+
}
156+
157+
async validateFileWithDiff(filePath, diffInfo) {
158+
try {
159+
if (diffInfo.isDeletedFile) {
160+
return [];
161+
}
162+
163+
if (!fs.existsSync(filePath)) {
164+
console.log(` ⚠️ File not found locally: ${filePath}`);
165+
return [{
166+
file: filePath,
167+
line: 1,
168+
message: `File not found locally (this is normal for local testing)`,
169+
severity: 'warning'
170+
}];
171+
}
172+
173+
const originalContent = fs.readFileSync(filePath, 'utf8');
174+
175+
// Generate filtered content
176+
const { content: filteredContent, lineMapping, changedLines } =
177+
this.diffParser.generateFilteredContent(originalContent, diffInfo);
178+
179+
// Apply all rules
180+
const allIssues = this.applyAllRules(filteredContent, filePath, { lineMapping, changedLines, diffInfo });
181+
182+
// Filter for changed lines only
183+
return this.filterIssuesForChangedLines(allIssues, changedLines, lineMapping, diffInfo);
184+
185+
} catch (error) {
186+
return [{
187+
file: filePath,
188+
line: 1,
189+
message: `Validation error: ${error.message}`,
190+
severity: 'error'
191+
}];
192+
}
193+
}
194+
195+
applyAllRules(content, filePath, diffContext) {
196+
const allIssues = [];
197+
const allRules = this.ruleParser.getAllRules();
198+
199+
allRules.forEach(rule => {
200+
try {
201+
const issues = rule.validator(content, filePath);
202+
issues.forEach(issue => {
203+
issue.ruleId = rule.id;
204+
// Map line numbers if we have diff context
205+
if (diffContext && diffContext.lineMapping) {
206+
const originalLine = diffContext.lineMapping[issue.line];
207+
if (originalLine) {
208+
issue.line = originalLine;
209+
}
210+
}
211+
});
212+
allIssues.push(...issues);
213+
} catch (error) {
214+
// Ignore rule errors for simplicity
215+
}
216+
});
217+
218+
return allIssues;
219+
}
220+
221+
filterIssuesForChangedLines(issues, changedLines, lineMapping, diffInfo) {
222+
if (!changedLines || diffInfo.isNewFile) {
223+
return issues;
224+
}
225+
226+
const actuallyChangedLines = new Set([
227+
...changedLines.added,
228+
...changedLines.modified
229+
]);
230+
231+
return issues.filter(issue => {
232+
if (actuallyChangedLines.has(issue.line)) {
233+
return true;
234+
}
235+
236+
// Simple proximity check for contextual rules
237+
for (const changedLine of actuallyChangedLines) {
238+
if (Math.abs(issue.line - changedLine) <= 2) {
239+
return true;
240+
}
241+
}
242+
243+
return false;
244+
});
245+
}
246+
}
247+
248+
// Command line interface
249+
async function main() {
250+
const args = process.argv.slice(2);
251+
252+
if (args.length === 0) {
253+
console.log('Usage: node test-local-diff.js <diff-file> [pr-number]');
254+
console.log('');
255+
console.log('Examples:');
256+
console.log(' node test-local-diff.js pr-2439.diff');
257+
console.log(' node test-local-diff.js pr-2439.diff 2439');
258+
console.log('');
259+
console.log('To get a diff file:');
260+
console.log(' curl https://patch-diff.githubusercontent.com/raw/strapi/documentation/pull/2439.diff > pr-2439.diff');
261+
process.exit(1);
262+
}
263+
264+
const diffFile = args[0];
265+
const prNumber = args[1] ? parseInt(args[1]) : null;
266+
267+
const tester = new LocalDiffTester(diffFile, prNumber);
268+
await tester.testValidation();
269+
}
270+
271+
if (require.main === module) {
272+
main().catch(console.error);
273+
}
274+
275+
module.exports = LocalDiffTester;

0 commit comments

Comments
 (0)