Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_BasicCodeFormatterPlugin.c
Go to the documentation of this file.
1#ifdef WORKBENCH
2
6
7// ###############
8// It is recommended to be VERY CAREFUL with breakpoints here
9// as GetCurrentFile, GetLinesCount, GetLineText, SETLineText etc may target this file
10// Once a breakpoint is hit (without FileIO), switch back to the edited file and press F5
11// ###############
12
13// TODO: ref/notnull method (?? e.g ref notnull ScriptInvoker GetOnHealthChanged(bool createNew = true))
14// TODO: detect methods without separator (beware of comments / doxygen comments)
15// TODO: detect unused private/protected class variables / methods (method variables... later.)
16// TODO: detect pointer declaration in loops (for/foreach/while)
17// TODO: detect missing empty line after an if two-liner (or if-else)
18
20 name: "Basic Code Formatter",
21 shortcut: "Ctrl+Shift+K",
22 wbModules: { "ScriptEditor" },
23 awesomeFontCode: 0xF036)]
24class SCR_BasicCodeFormatterPlugin : WorkbenchPlugin
25{
26 /*
27 Category: Formatting
28 */
29
30 [Attribute(defvalue: "1", desc: "Fix e.g 'for( int' → 'for (int', ';;' → ';', ') ;' → ');', 'NULL' → 'null', ',' → ', ', etc.", category: "Formatting")]
31 protected bool m_bGeneralFormatting;
32
33 [Attribute(defvalue: "1", desc: "Remove empty spaces at the end of code lines", category: "Formatting")]
34 protected bool m_bTrimLineEnds;
35
36 [Attribute(defvalue: "1", desc: "Replace four-spaces tabs with the tab character and remove spaces mixed with tabs", category: "Formatting")]
37 protected bool m_bFixTabs;
38
39 [Attribute(defvalue: "1", desc: "Fix method separators (" + SCR_StringHelper.DOUBLE_SLASH + "---)", category: "Formatting")]
40 protected bool m_bFixMethodSeparators;
41
42// [Attribute(defvalue: "1", desc: "Replace consecutive empty lines with only one", category: "Formatting")]
43// protected bool m_bRemoveMultiEmptyLines;
44
45 [Attribute(defvalue: "1", desc: "Add one final line return if the file is missing one", category: "Formatting")]
46 protected bool m_bAddFinalLineReturn;
47
48 [Attribute(desc: "Accepted class/enum prefixes (e.g 'SCR_', 'TAG_', etc) - an underscore is automatically added if missing. 'SCR_' is automatically whitelisted.\nCase-sensitive", category: "Formatting")]
49 protected ref array<string> m_aAcceptedScriptPrefixes;
50
51 /*
52 Category: Batch Process
53 */
54
55 [Attribute(defvalue: "0", desc: "Process all script files in the addon defined below (only process the currently opened file if unchecked)", category: "Batch Process")]
56 protected bool m_bBatchProcessAddon;
57
58 [Attribute(defvalue: "0", uiwidget: UIWidgets.ComboBox, desc: "Addon in which script files will be batch-formatted (if \"Batch Process Addon\" above is selected)", enums: SCR_ParamEnumArray.FromAddons(titleFormat: 2, hideCoreModules: 2), category: "Batch Process")]
59 protected int m_iAddon;
60
61 [Attribute(defvalue: "1", desc: "Only log reports where processing did something to the file", category: "Batch Process")]
62 protected bool m_bOnlyLogChanges;
63
64 [Attribute(defvalue: "10", uiwidget: UIWidgets.Slider, desc: "Max number of formatting reports to display - use -1 to display all reports", params: "-1 100 1", category: "Batch Process")]
65 protected int m_iMaxLoggedReports;
66
67 /*
68 Category: Options
69 */
70
71 [Attribute(defvalue: "0", desc: "Demo mode only logs (possible) fixes and does not modify the file at all", category: "Options")]
72 protected bool m_bDemoMode;
73
74 [Attribute(defvalue: "1", desc: "Display a dialog on execution if unchecked - dialog is always displayed when batch-processing is enabled", category: "Options")]
75 protected bool m_bSilentExecution;
76
77 [Attribute(defvalue: "1", desc: "Only format DiffCommand-detected modified lines instead of all of them", category: "Options")]
78 protected bool m_bOnlyFormatModifiedLines;
79
80 [Attribute(defvalue: "1", desc: "Write fixes in console log", category: "Options")]
81 protected bool m_bLogFixes;
82
83 [Attribute(defvalue: "1", desc: "Show potential improvements report", category: "Options")]
84 protected bool m_bReportFindings;
85
86 [Attribute(defvalue: "scripts/xxx/generated/", desc: "Directories in which to avoid formatting (case-insensitive, no wildcards)", category: "Options")]
87 protected ref array<string> m_aExcludedDirectories;
88
89 [Attribute(desc: "Spell check config - set it to null to reset its default values", category: "Options")]
90 protected ref SCR_BasicCodeFormatterSpellCheckConfig m_SpellCheckConfig;
91
92 [Attribute(defvalue: "1", desc: "Whether or not display flagged words details in the report", category: "Options")]
93 protected bool m_bShowSpellCheckFindingsDetails;
94
95 /*
96 Category: Advanced
97 */
98
99 [Attribute(defvalue: DEFAULT_DIFF_CMD, desc: "The command line used to generate the diff file\n%1 = absolute target filepath\n%2 = absolute destination filepath", category: "Advanced")]
100 protected string m_sDiffCommand;
101
102 // member variables
103
104 protected ref array<ref array<string>> m_aGeneralFormatting_Start;
105 protected ref array<ref array<string>> m_aGeneralFormatting_Middle;
106 protected ref array<ref array<string>> m_aGeneralFormatting_End;
107
108 protected ref array<string> m_aForbiddenDivisions;
109 protected ref array<string> m_aPrefixLineChecks;
110 protected ref array<string> m_aPrefixChecks;
111
112 protected ref array<string> m_aForbiddenDirectories;
113
114 protected ref array<string> m_aForForEachWhileArray;
115 protected ref array<string> m_aIfForForEachWhileArray;
116 protected ref array<string> m_aEndBracketSemicolonArray;
117 protected ref array<string> m_aNewArrayNewRefArray;
118 protected ref array<string> m_aScriptInvokerArray;
119
120 protected ref map<string, string> m_mVariableTypePrefixes;
121 protected ref map<string, string> m_mVariableTypePrefixesStart;
122 protected ref map<string, string> m_mVariableTypePrefixesEnd;
123
125 protected ref map<string, string> m_mForbiddenWords; // detection only, no replacement due to casing
126
127 // constants
128
129 protected static const int LINE_NUMBER_LIMIT = 12;
130 protected static const string LINE_NUMBER_RANGE = "%1-%2";
131
132 protected static const string LOG_SEPARATOR = "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -";
133 protected static const string BRACKET_OPEN = "{"; // avoids {} Script Editor indent shenanigans
134 protected static const string BRACKET_CLOSE = "}";
135 protected static const string MEMBER_PREFIX = "m_";
136 protected static const string STATIC_PREFIX = "s_";
137
138 // can be string-hardcoded without too much risks
139 protected static const ref array<string> NATIVE_TYPES = {
140 "bool", "float", "int", "string", "typename", "vector",
141 "FactionKey", "LocalizedString", "ResourceName",
142 };
143
144 // "varName' '= 42", "varName, otherVarName", "varName= 42", "varName;" - tabs are replaced by spaces in HasBadVariableNaming
145 protected static const ref array<string> VARIABLE_NAME_ENDING = { SCR_StringHelper.SPACE, ",", "=", ";", SCR_StringHelper.SLASH }; // technically, "everything but [a-zA-Z0-9_] and []"
146
147 protected static const string METHOD_SEPARATOR = SCR_StringHelper.DOUBLE_SLASH + "------------------------------------------------------------------------------------------------";
148 protected static const string DIFF_FILENAME = "tempDiffFile.txt";
149
150 // TODO: start /B svn diff "%1" > "%2" + delay? ResourceManager.WaitForFile?
151 protected static const string DEFAULT_DIFF_CMD = "cmd /c svn diff \"%1\" > \"%2\""; // %1 = absolute target filepath, %2 = absolute destination filepath
152 protected static const ref array<string> FORMAT_IGNORE = { SCR_StringHelper.DOUBLE_SLASH, SCR_StringHelper.SLASH + "*", SCR_StringHelper.DOUBLE_QUOTE };
153 protected static const ref array<string> FORCED_PREFIXES = { "SCR_" };
154 protected static const ref array<string> EXCLUDED_DIRECTORIES = {
155 "scripts/Core/generated/",
156 "scripts/GameLib/generated/",
157 "scripts/Game/generated/",
158 "scripts/WorkbenchGameCommon/generated/",
159 };
160 protected static const string GENERATED_SCRIPT_WARNING = "Do not modify, this script is generated"; // must be the exact line, tabs included if any
161 protected static const ResourceName SPELLCHECK_CONFIG = "{53D7DE332A43449F}Configs/Workbench/ScriptEditor/BasicCodeFormatterPlugin/BasicCodeFormatterSpellCheckConfig.conf";
162
163 //------------------------------------------------------------------------------------------------
165 protected override void Run()
166 {
167 if (m_bBatchProcessAddon || !m_bSilentExecution)
168 {
169 if (!Workbench.ScriptDialog("Configure 'Basic Code formatter' plugin", "Configure formatting options", this))
170 return;
171 }
172
173 Initialise();
174
175 if (m_bBatchProcessAddon)
176 RunAddonFilesBatchProcess();
177 else
178 RunCurrentFile();
179 }
180
181 //------------------------------------------------------------------------------------------------
183 void RunForced()
184 {
185 bool batchProcessAddon = m_bBatchProcessAddon;
186 bool demoMode = m_bDemoMode;
187 bool onlyFormatModifiedLines = m_bOnlyFormatModifiedLines;
188
189 m_bBatchProcessAddon = false;
190 m_bOnlyFormatModifiedLines = false;
191 m_bDemoMode = false;
192
193 Run();
194
195 m_bBatchProcessAddon = batchProcessAddon;
196 m_bDemoMode = demoMode;
197 m_bOnlyFormatModifiedLines = onlyFormatModifiedLines;
198 }
199
200 //------------------------------------------------------------------------------------------------
202 protected void Initialise()
203 {
204 ScriptEditor scriptEditor = Workbench.GetModule(ScriptEditor);
205 if (!scriptEditor)
206 {
207 Print("Script Editor API is not available", LogLevel.ERROR);
208 return;
209 }
210
211 if (m_aPrefixChecks)
212 m_aPrefixChecks.Clear();
213 else
214 m_aPrefixChecks = {};
215
216 for (int i = m_aAcceptedScriptPrefixes.Count() - 1; i >= 0; i--)
217 {
218 string prefix = m_aAcceptedScriptPrefixes[i].Trim();
219 if (!prefix) // !IsEmpty for perf
220 {
221 m_aAcceptedScriptPrefixes.RemoveOrdered(i);
222 continue;
223 }
224
225 if (!prefix.EndsWith("_"))
226 m_aAcceptedScriptPrefixes[i] = prefix + "_";
227 }
228
229 array<string> toRemove = {};
230 foreach (string forcedPrefix : FORCED_PREFIXES)
231 {
232 if (!m_aAcceptedScriptPrefixes.Contains(forcedPrefix))
233 {
234 m_aAcceptedScriptPrefixes.Insert(forcedPrefix);
235 toRemove.Insert(forcedPrefix);
236 }
237 }
238
239 foreach (string prefix : m_aAcceptedScriptPrefixes)
240 {
241 foreach (string prefixLineCheck : m_aPrefixLineChecks)
242 {
243 m_aPrefixChecks.Insert(prefixLineCheck + prefix);
244 }
245 }
246
247 foreach (string forcedPrefix : toRemove)
248 {
249 m_aAcceptedScriptPrefixes.RemoveItem(forcedPrefix);
250 }
251
252 // excluded directories
253 m_aForbiddenDirectories = {};
254 m_aForbiddenDirectories.Copy(EXCLUDED_DIRECTORIES);
255 foreach (int i, string excludedDirectory : m_aExcludedDirectories)
256 {
257 if (!excludedDirectory)
258 continue;
259
260 if (!excludedDirectory.EndsWith(SCR_StringHelper.SLASH)) // add final slash
261 excludedDirectory += SCR_StringHelper.SLASH;
262
263 m_aExcludedDirectories[i] = excludedDirectory; // update user setting
264
265 excludedDirectory.ToLower();
266 m_aForbiddenDirectories.Insert(excludedDirectory);
267 }
268
269 if (m_mForbiddenWords)
270 m_mForbiddenWords.Clear();
271 else
272 m_mForbiddenWords = new map<string, string>();
273
274 if (!m_SpellCheckConfig)
275 m_SpellCheckConfig = SCR_ConfigHelperT<SCR_BasicCodeFormatterSpellCheckConfig>.GetConfigObject(SPELLCHECK_CONFIG);
276
277 if (m_SpellCheckConfig && m_SpellCheckConfig.m_aEntries)
278 {
279 foreach (SCR_BasicCodeFormatterSpellCheckConfig_ForbiddenWordEntry entry : m_SpellCheckConfig.m_aEntries)
280 {
281 if (!entry.m_bEnabled)
282 continue;
283
284 string mistake = SCR_StringHelper.Filter(entry.m_sMistake, SCR_StringHelper.ALPHANUMERICAL + SCR_StringHelper.STAR);
285 if (!mistake) // .IsEmpty()
286 continue;
287
288 // case-insensitive
289 mistake.ToLower();
290
291 if (m_mForbiddenWords.Contains(mistake))
292 continue;
293
294 string correction = entry.m_sCorrection;
295 correction.TrimInPlace();
296 if (correction)
297 correction = string.Format("use \"%1\" instead", correction);
298
299 string comment = entry.m_sComment;
300 comment.TrimInPlace();
301
302 if (comment) // !.IsEmpty()
303 {
304 if (correction) // !.IsEmpty()
305 correction = string.Format("%1 (%2)", correction, comment);
306 else
307 correction = comment;
308 }
309
310 m_mForbiddenWords.Insert(mistake, correction);
311 }
312 }
313 }
314
315 //------------------------------------------------------------------------------------------------
317 protected void RunCurrentFile()
318 {
319 string currentFile;
320 ScriptEditor scriptEditor = Workbench.GetModule(ScriptEditor);
321 scriptEditor.GetCurrentFile(currentFile);
322 SCR_BasicCodeFormatterPluginFileReport report = ProcessFile(currentFile, false);
323 if (report)
324 PrintReport(report, m_bLogFixes, m_bReportFindings); // always print current file's report
325 }
326
327 //------------------------------------------------------------------------------------------------
329 protected void RunAddonFilesBatchProcess()
330 {
331 string absoluteAddonDirectory;
332 if (!SCR_AddonTool.GetAddonAbsolutePath(m_iAddon, string.Empty, absoluteAddonDirectory, true))
333 {
334 Print("Wrong addon selected: " + SCR_AddonTool.GetAddonID(m_iAddon) + " (read-only?)", LogLevel.ERROR);
335 return;
336 }
337
338 string addonName = SCR_AddonTool.GetAddonID(m_iAddon);
339 string addonFileSystem = SCR_AddonTool.ToFileSystem(addonName);
340 int addonFileSystemLength = addonFileSystem.Length();
341 int addonFileSystemLengthPlus3 = addonFileSystemLength + 3; // e.g "$FS:a.c"
342 array<string> scriptFiles = SCR_WorkbenchHelper.SearchWorkbenchFiles({ "c" }, null, addonFileSystem);
343
344 array<string> relativeFilePaths = {};
345 foreach (string scriptFile : scriptFiles)
346 {
347 if (!scriptFile) // .IsEmpty()
348 continue;
349
350 int scriptFileLength = scriptFile.Length();
351 if (scriptFileLength < addonFileSystemLengthPlus3) // .IsEmpty()
352 continue;
353
354 relativeFilePaths.Insert(scriptFile.Substring(addonFileSystemLength, scriptFileLength - scriptFileLength));
355 }
356
357 int editableScriptsCount = relativeFilePaths.Count();
358 if (editableScriptsCount < 1)
359 {
360 Print("No script files found in " + addonName + " - leaving", LogLevel.WARNING);
361 return;
362 }
363
364 if (!Workbench.ScriptDialog(
365 "Warning",
366 "You are about to format " + editableScriptsCount + " \"" + addonName + "\" addon script files.\n\n" + "Continue?",
367 new WorkbenchDialog_OKCancel())
368 )
369 return;
370
371 array<ref SCR_BasicCodeFormatterPluginFileReport> reports = ProcessFiles(relativeFilePaths, true);
372
373 if (m_iMaxLoggedReports < 1)
374 return;
375
376 int displayedReports;
377 foreach (SCR_BasicCodeFormatterPluginFileReport report : reports)
378 {
379 if (!report)
380 continue;
381
382 if (m_bOnlyLogChanges && report.IsClean())
383 continue;
384
385 PrintReport(report, m_bLogFixes, m_bReportFindings);
386
387 displayedReports++;
388 if (m_iMaxLoggedReports > 0 && displayedReports >= m_iMaxLoggedReports)
389 break;
390 }
391 }
392
393 //------------------------------------------------------------------------------------------------
399 protected array<ref SCR_BasicCodeFormatterPluginFileReport> ProcessFiles(array<string> relativeFilePaths, bool useFileIO)
400 {
401 string currentFile;
402 ScriptEditor scriptEditor = Workbench.GetModule(ScriptEditor);
403 scriptEditor.GetCurrentFile(currentFile);
404
405 SCR_BasicCodeFormatterPluginFileReport report;
406 array<ref SCR_BasicCodeFormatterPluginFileReport> result = {};
407 foreach (string relativeFilePath : relativeFilePaths)
408 {
409 report = ProcessFile(relativeFilePath, useFileIO && relativeFilePath != currentFile); // do NOT use FileIO on the current file - the Script Editor may lose edits track
410 result.Insert(report);
411 }
412
413 return result;
414 }
415
416 //------------------------------------------------------------------------------------------------
422 protected SCR_BasicCodeFormatterPluginFileReport ProcessFile(string relativeFilePath, bool useFileIO)
423 {
424 if (SCR_StringHelper.IsEmptyOrWhiteSpace(relativeFilePath))
425 {
426 Print("Provided relative file path is empty", LogLevel.WARNING);
427 return null;
428 }
429
430 foreach (string forbiddenDir : m_aForbiddenDirectories)
431 {
432 if (relativeFilePath.StartsWith(forbiddenDir))
433 {
434 PrintFormat("File is in excluded directory, skipping (%1) - %2", forbiddenDir, relativeFilePath, level: LogLevel.NORMAL);
435 return null;
436 }
437 }
438
439 bool isReadOnly = false;
440 bool demoMode = m_bDemoMode;
441
442 // checking the file is modifiable
443 string absoluteFilePath;
444 if (!Workbench.GetAbsolutePath(relativeFilePath, absoluteFilePath, true))
445 {
446 Print("Cannot find the absolute file path, switching to readonly mode - is file read-only? " + relativeFilePath, LogLevel.WARNING);
447 demoMode = true;
448 isReadOnly = true;
449 }
450
451 SCR_BasicCodeFormatterPluginFileReport report = new SCR_BasicCodeFormatterPluginFileReport(relativeFilePath, relativeFilePath == __FILE__);
452
453 bool reportFindings = !report.m_bIsPluginFile && m_bReportFindings;
454 bool doGeneralFormatting = !report.m_bIsPluginFile && m_bGeneralFormatting;
455
456 bool isPreviousLineEmpty;
457 bool emptyLineGroupLogged;
458
459 bool isInRepository;
460
461 if (m_bOnlyFormatModifiedLines && !isReadOnly)
462 {
463 report.m_iDiffTime = System.GetTickCount();
464 report.m_aDiffLines = GetFileModifiedLineNumbers(absoluteFilePath, isInRepository);
465 report.m_iDiffTime = System.GetTickCount(report.m_iDiffTime);
466
467 if (report.m_aDiffLines && report.m_aDiffLines.IsEmpty()) // no changes found
468 {
469 if (isInRepository) // an unchanged committed file must not be formatted
470 doGeneralFormatting = false;
471 else // a non-committed file gets fully checked
472 report.m_aDiffLines = null;
473 }
474 }
475
476 array<string> pieces; // instanciation not needed (see GetIndentAndLineContentAsPieces)
477 array<string> friendlyWords = {};
478 /*
479 Start
480 */
481 const int startTick = System.GetTickCount();
482
483 array<string> lines = ReadFileContent(relativeFilePath, useFileIO);
484 report.m_iReadTime = System.GetTickCount(startTick);
485
486 if (lines.Contains(GENERATED_SCRIPT_WARNING))
487 {
488 Print("Skipping generated script " + relativeFilePath, LogLevel.NORMAL);
489 return null;
490 }
491
492 ScriptEditor scriptEditor = Workbench.GetModule(ScriptEditor);
493
494 int linesCount = lines.Count(); // get array count instead of incrementing a variable
495 report.m_iLinesTotal = linesCount;
496
497 bool prevFormatThisLine;
498 string prevFullLine;
499 string prevContent;
500 foreach (int lineNumber, string fullLine : lines)
501 {
502 int lineNumberPlus1 = lineNumber + 1;
503 bool formatThisLine = !report.m_aDiffLines || report.m_aDiffLines.Contains(lineNumberPlus1); // null = format everything
504 if (!formatThisLine && !reportFindings)
505 {
506 prevFormatThisLine = formatThisLine; // false
507 prevFullLine = fullLine;
508 prevContent = fullLine.Trim();
509 continue;
510 }
511
512 string indentation;
513 GetIndentAndLineContentAsPieces(fullLine, indentation, pieces);
514 int piecesCount = pieces.Count();
515
516 if (formatThisLine)
517 {
518 if (m_bTrimLineEnds)
519 {
520 int endSpacesRemoved;
521 if (piecesCount < 1)
522 {
523 endSpacesRemoved = indentation.Length();
524 indentation = string.Empty;
525 }
526 else
527 {
528 string endPiece = pieces[piecesCount - 1];
529 endPiece = SCR_StringHelper.TrimRight(endPiece);
530 endSpacesRemoved = pieces[piecesCount - 1].Length() - endPiece.Length();
531 if (endSpacesRemoved > 0)
532 pieces.Set(piecesCount - 1, endPiece);
533 }
534
535 if (endSpacesRemoved > 0)
536 {
537 report.m_iEndSpacesRemovedTotal += endSpacesRemoved;
538 report.m_aTrimmings.Insert(lineNumberPlus1);
539 }
540 }
541
542 if (m_bFixTabs)
543 {
544 int fourSpacesReplaced = indentation.Replace(SCR_StringHelper.QUADRUPLE_SPACE, SCR_StringHelper.TAB);
545 int spaceInTabsReplaced = indentation.Replace(SCR_StringHelper.SPACE, string.Empty);
546
547 if (fourSpacesReplaced > 0 || spaceInTabsReplaced > 0)
548 {
549 report.m_iFourSpacesReplacedTotal += fourSpacesReplaced;
550 report.m_iSpaceInTabsReplacedTotal += spaceInTabsReplaced;
551 report.m_aFixedIndentations.Insert(lineNumberPlus1);
552 }
553 }
554
555 if (m_bFixMethodSeparators)
556 {
557 if (piecesCount == 1 && pieces[0].StartsWith(SCR_StringHelper.DOUBLE_SLASH + "---") && pieces[0].EndsWith("---") && !SCR_StringHelper.Filter(pieces[0], "/-", true) && pieces[0] != METHOD_SEPARATOR)
558 {
559 pieces.Set(0, METHOD_SEPARATOR);
560 report.m_iMethodSeparatorFixedTotal++;
561 }
562 }
563
564 if (doGeneralFormatting)
565 report.m_iGeneralFormattingTotal += GeneralFormatting(indentation, pieces);
566 }
567
568 string currContent = SCR_StringHelper.Join(string.Empty, pieces);
569 if (reportFindings)
570 {
571 if (indentation != SCR_StringHelper.TAB && currContent.StartsWith(SCR_StringHelper.DOUBLE_SLASH + "---") && currContent.EndsWith("---"))
572 report.m_aBadSeparatorFound.Insert(lineNumberPlus1);
573
574 string fullLineLC = SCR_StringHelper.FormatValueNameToUserFriendly(fullLine);
575 fullLineLC.ToLower();
576 fullLineLC.Split(SCR_StringHelper.SPACE, friendlyWords, true);
577 foreach (string friendlyWord : friendlyWords) // all lowercase
578 {
579 foreach (string forbiddenWord, string correction : m_mForbiddenWords)
580 {
581 if (SCR_StringHelper.SimpleStarSearchMatches(friendlyWord, forbiddenWord, false, false))
582 {
583 if (report.m_mForbiddenWordFound.Contains(forbiddenWord))
584 report.m_mForbiddenWordFound.Get(forbiddenWord).Insert(lineNumberPlus1);
585 else
586 report.m_mForbiddenWordFound.Insert(forbiddenWord, { lineNumberPlus1 });
587
588 break; // one match per word is enough
589 }
590 }
591 }
592
593 string findingsString = currContent.Trim();
594 bool isCurrentLineEmpty = !findingsString; // !IsEmpty for perf
595 if (isPreviousLineEmpty)
596 {
597 if (isCurrentLineEmpty)
598 {
599 if (!emptyLineGroupLogged)
600 {
601 report.m_aDoubleEmptyLineFound.Insert(lineNumber); // previous line number
602 emptyLineGroupLogged = true;
603 }
604 }
605 else
606 {
607 emptyLineGroupLogged = false;
608 }
609 }
610 else // let's check for scope opening/endings
611 {
612 if (isCurrentLineEmpty)
613 {
614 if (prevContent.StartsWith("{")) // }
615 report.m_aSuperfluousEmptyLineFound.Insert(lineNumberPlus1);
616 }
617 else
618 { // { {
619 if (prevFullLine.StartsWith("\t\t") && prevContent.StartsWith("}") && !currContent.StartsWith("}") && !currContent.StartsWith("else"))
620 report.m_aMissingEmptyLineFound.Insert(lineNumberPlus1);
621 }
622 }
623
624 findingsString = string.Empty;
625 array<string> splits = {};
626 foreach (string piece : pieces)
627 {
628 if (piece.StartsWith(SCR_StringHelper.DOUBLE_SLASH))
629 {
630 findingsString = SCR_StringHelper.TrimRight(findingsString);
631 break; // cheaper
632 }
633
634 if (!SCR_StringHelper.StartsWithAny(piece, FORMAT_IGNORE)) // don't check comments
635 findingsString += piece;
636 }
637
638 if (findingsString) // !IsEmpty for perf
639 {
640 if (findingsString.Contains(" = new ") && !findingsString.Contains("(")) // )
641 report.m_aNewWithoutParentheseFound.Insert(lineNumberPlus1);
642
643 if (SCR_StringHelper.ContainsAny(findingsString, m_aNewArrayNewRefArray))
644 report.m_aNewArrayFound.Insert(lineNumberPlus1);
645
646 if (indentation.StartsWith("\t\t") && findingsString.StartsWith("ref "))
647 report.m_aUselessRefFound.Insert(lineNumberPlus1);
648
649 if (findingsString.Contains("auto "))
650 report.m_aAutoKeywordFound.Insert(lineNumberPlus1);
651
652 if (findingsString.Contains("autoptr "))
653 report.m_aAutoptrKeywordFound.Insert(lineNumberPlus1);
654
655 if (findingsString.StartsWith("for (")) // )
656 {
657 findingsString.Split(";", splits, true);
658 if (splits[1].Contains("(")) // )
659 report.m_aForCountCall.Insert(lineNumberPlus1);
660 }
661
662 foreach (string forbiddenDivision : m_aForbiddenDivisions)
663 {
664 if (findingsString.Contains(forbiddenDivision))
665 {
666 report.m_aDivideByXFound.Insert(lineNumberPlus1);
667 break;
668 }
669 }
670
671 if (
672 lineNumber < linesCount - 1
673 && SCR_StringHelper.StartsWithAny(findingsString, m_aForForEachWhileArray)
674 && SCR_StringHelper.CountOccurrences(findingsString, "(") == SCR_StringHelper.CountOccurrences(findingsString, ")") // e.g multiline "while"
675 )
676 {
677 string nextLine = lines[lineNumberPlus1];
678 if (!nextLine.Trim().StartsWith(BRACKET_OPEN))
679 report.m_aUnscopedLoopFound.Insert(lineNumberPlus1);
680 }
681
682 if (SCR_StringHelper.StartsWithAny(findingsString, m_aIfForForEachWhileArray) && SCR_StringHelper.EndsWithAny(findingsString, m_aEndBracketSemicolonArray))
683 report.m_aOneLinerFound.Insert(lineNumberPlus1);
684
685 if ((!isInRepository || formatThisLine) && HasBadVariableNaming(indentation, findingsString)) // )] // do not suggest renaming existing attributes, they are most likely public by now
686 report.m_aBadVariableNamingFound.Insert(lineNumberPlus1);
687
688 if (SCR_StringHelper.ContainsAny(findingsString, m_aScriptInvokerArray))
689 report.m_aBadScriptInvokerFound.Insert(lineNumberPlus1);
690
691 if (findingsString.StartsWith("Print") && findingsString.EndsWith(";"))
692 {
693 if (findingsString.StartsWith("Print(")) // )
694 {
695 if (!findingsString.Contains("LogLevel.") && !findingsString.Contains("logLevel)"))
696 report.m_aWildPrintFound.Insert(lineNumberPlus1);
697
698 if (findingsString.Contains("Print(string.Format(")) // ))
699 report.m_aPrintToPrintFormatFound.Insert(lineNumberPlus1);
700 }
701 else
702 if (findingsString.StartsWith("PrintFormat(")) // )
703 {
704 if (!findingsString.Contains("level:"))
705 report.m_aWildPrintFound.Insert(lineNumberPlus1);
706 }
707 }
708
709 if (findingsString.Contains("enums: ParamEnumArray.FromEnum(")) // )
710 report.m_aEnumsToEnumTypeFound.Insert(lineNumberPlus1);
711
712 if (SCR_StringHelper.StartsWithAny(findingsString, m_aPrefixLineChecks) && !SCR_StringHelper.StartsWithAny(findingsString, m_aPrefixChecks))
713 report.m_aNonPrefixedClassOrEnumFound.Insert(lineNumberPlus1);
714 }
715
716 isPreviousLineEmpty = isCurrentLineEmpty;
717 }
718
719 prevFormatThisLine = formatThisLine;
720
721 string finalLine = indentation + currContent;
722 prevFullLine = finalLine;
723 prevContent = currContent;
724
725 if (finalLine == fullLine)
726 continue;
727
728 report.m_iLinesEdited++;
729
730 if (demoMode)
731 continue;
732
733 if (useFileIO)
734 {
735 lines[lineNumber] = finalLine;
736 }
737 else
738 {
739 if (scriptEditor.SetOpenedResource(relativeFilePath)) // forces the edited file to be focused
740 scriptEditor.SetLineText(finalLine, lineNumber); // directly use Script Editor to avoid going through lines twice
741 }
742 }
743 /*
744 Finish
745 */
746
747 if (!demoMode && m_bAddFinalLineReturn && linesCount > 0 && lines[linesCount - 1] != string.Empty)
748 {
749 if (useFileIO)
750 lines.Insert(string.Empty);
751 else
752 AddFinalLineReturnToCurrentFile();
753
754 report.m_bHasAddedFinalLineReturn = true;
755 }
756
757 // later, with proper line insertion/deletion
758// if (m_bRemoveMultiEmptyLines)
759// {
760// if (useFileIO)
761// doSomething();
762// else
763// RemoveEmptyLinesInCurrentFile();
764// }
765
766 // write time!
767 if (!demoMode && useFileIO)
768 SCR_FileIOHelper.WriteFileContent(relativeFilePath, lines);
769
770 report.m_iFormatTime = System.GetTickCount(startTick) - report.m_iReadTime; // startTick happens before read time measurement
771
772 return report;
773 }
774
775 //------------------------------------------------------------------------------------------------
780 protected array<string> ReadFileContent(string relativeFilePath, bool useFileIO)
781 {
782 if (useFileIO)
783 return SCR_FileIOHelper.ReadFileContent(relativeFilePath);
784
785 // open file in Script Editor otherwise
786 array<string> result = {};
787 ScriptEditor scriptEditor = Workbench.GetModule(ScriptEditor);
788 scriptEditor.SetOpenedResource(relativeFilePath); // open tab
789
790 for (int lineNumber, linesCount = scriptEditor.GetLinesCount(); lineNumber < linesCount; lineNumber++)
791 {
792 string line;
793 // forces the read file to be focused - HEAVY on performance (e.g from 100 to 900ms) - use only for breakpoint debug
794 // if (scriptEditor.SetOpenedResource(relativeFilePath))
795 scriptEditor.GetLineText(line, lineNumber);
796
797 result.Insert(line);
798 }
799
800 return result;
801 }
802
803 //------------------------------------------------------------------------------------------------
808 protected void PrintReport(notnull SCR_BasicCodeFormatterPluginFileReport report, bool printFixes, bool printFindings)
809 {
810 if (printFixes)
811 {
812 array<string> reportArray = {};
813
814 if (report.m_iLinesEdited > 0)
815 reportArray.Insert(string.Format("%1 lines changed", report.m_iLinesEdited));
816
817 if (!report.m_aTrimmings.IsEmpty())
818 reportArray.Insert(report.m_aTrimmings.Count().ToString() + " line trimmings"); // at line(s) " + JoinLineNumbers(report.m_aTrimmings));
819
820 if (!report.m_aFixedIndentations.IsEmpty())
821 reportArray.Insert(report.m_aFixedIndentations.Count().ToString() + "× indent fixes at lines " + JoinLineNumbers(report.m_aFixedIndentations));
822
823 if (report.m_iGeneralFormattingTotal > 0)
824 reportArray.Insert(string.Format("%1 formattings", report.m_iGeneralFormattingTotal));
825
826 if (report.m_iEndSpacesRemovedTotal > 0)
827 reportArray.Insert(string.Format("%1 end spaces trimming", report.m_iEndSpacesRemovedTotal));
828
829 if (report.m_iFourSpacesReplacedTotal > 0)
830 reportArray.Insert(string.Format("%1 4-spaces indent -> tabs replaced", report.m_iFourSpacesReplacedTotal));
831
832 if (report.m_iSpaceInTabsReplacedTotal > 0)
833 reportArray.Insert(string.Format("%1 space(s) in indentation removed", report.m_iSpaceInTabsReplacedTotal));
834
835 if (report.m_iMethodSeparatorFixedTotal > 0)
836 reportArray.Insert(string.Format("%1 method separators fixed", report.m_iMethodSeparatorFixedTotal));
837
838 if (report.m_bHasAddedFinalLineReturn)
839 reportArray.Insert("added final newline");
840
841 string reportLine = "";
842 if (m_bDemoMode && !reportArray.IsEmpty())
843 reportLine = "<span style='color: turquoise'>[DEMO]</span> ";
844
845 if (report.m_bIsPluginFile)
846 reportLine += "<span style='color: orange'>[PLUGIN FILE]</span> ";
847
848 reportLine += "<strong>" + FilePath.StripPath(report.m_sRelativeFilePath) + "</strong> - ";
849
850 if (reportArray.IsEmpty())
851 reportLine += "no fixes to report";
852 else
853 reportLine += SCR_StringHelper.Join(", ", reportArray, false);
854
855 reportLine += " (read: " + report.m_iReadTime + " ms, format: " + report.m_iFormatTime + " ms";
856
857 if (report.m_iDiffTime > 0)
858 reportLine += ", diff: " + report.m_iDiffTime + " ms";
859 else
860 report.m_iDiffTime = 0; // default value = -1
861
862 reportLine += " - total: " + (report.m_iReadTime + report.m_iFormatTime + report.m_iDiffTime) + " ms)";
863
864 Print((string)LOG_SEPARATOR, LogLevel.NORMAL);
865 Print((string)reportLine, LogLevel.NORMAL); // cast prevents "string reportLine = " print prefix
866
867 if (report.m_aDiffLines && !report.m_aDiffLines.IsEmpty())
868 {
869 int diffLinesCount = report.m_aDiffLines.Count();
870 float percentage = diffLinesCount / report.m_iLinesTotal * 100;
871 PrintFormat("Checked (%1/%2) edited lines %3 (%4%%)", diffLinesCount, report.m_iLinesTotal, JoinLineNumbers(report.m_aDiffLines), percentage.ToString(lenDec: 2), level: LogLevel.NORMAL); // not really ISO 31-0 compatible...
872 }
873 else
874 {
875 Print("Checked all " + report.m_iLinesTotal + " lines (100% of the file)", LogLevel.NORMAL);
876 }
877 }
878
879 if (printFindings && !report.m_bIsPluginFile)
880 {
881 if (!report.m_aNewWithoutParentheseFound.IsEmpty())
882 PrintFinding("new instance(s) without ()", report.m_aNewWithoutParentheseFound, "always use parentheses when instanciating a class");
883
884 if (!report.m_aUselessRefFound.IsEmpty())
885 PrintFinding("ref something", report.m_aUselessRefFound, "ref is not needed in script scope, only on class instance declaration");
886
887 if (!report.m_aNewArrayFound.IsEmpty())
888 PrintFinding("'new array<>'", report.m_aNewArrayFound, "replace by {} whenever possible");
889
890 if (!report.m_aBadSeparatorFound.IsEmpty())
891 PrintFinding("bad separator(s) detected", report.m_aBadSeparatorFound, "use separators only for methods, not for classes or anything else - keep the minimum amount of classes per file");
892
893 if (!report.m_aDoubleEmptyLineFound.IsEmpty())
894 PrintFinding("multiple consecutive empty lines", report.m_aDoubleEmptyLineFound, "leave only one");
895
896 if (!report.m_aSuperfluousEmptyLineFound.IsEmpty())
897 PrintFinding("superfluous empty line(s)", report.m_aSuperfluousEmptyLineFound, "an empty line is not welcome after the beginning of a scope");
898
899 if (!report.m_aMissingEmptyLineFound.IsEmpty())
900 PrintFinding("missing empty line(s)", report.m_aMissingEmptyLineFound, "have an empty line between the end of a scope (or an unscoped if/if-else) and code after that");
901
902 if (!report.m_aAutoKeywordFound.IsEmpty())
903 PrintFinding("'auto' keyword", report.m_aAutoKeywordFound, "use the direct type instead");
904
905 if (!report.m_aAutoptrKeywordFound.IsEmpty())
906 PrintFinding("'autoptr' keyword", report.m_aAutoptrKeywordFound, "remove if used on a standard variable, or replace with 'ref' if used for a member variable array");
907
908 if (!report.m_aForCountCall.IsEmpty())
909 PrintFinding("for-loop method call", report.m_aForCountCall, "a method is called every iteration; is it wanted? (usually arr.Count())");
910
911 if (!report.m_aDivideByXFound.IsEmpty())
912 PrintFinding("potentially avoidable division", report.m_aDivideByXFound, "use ' * 0.5' instead of ' / 2', ' * 0.1' instead of ' / 10' etc whenever possible");
913
914 if (!report.m_aUnscopedLoopFound.IsEmpty())
915 PrintFinding("loop without brackets", report.m_aUnscopedLoopFound, "always use brackets {} with for/foreach/while loops");
916
917 if (!report.m_aOneLinerFound.IsEmpty())
918 PrintFinding("one-liner", report.m_aOneLinerFound, "use a line return after an if/for/foreach/while condition");
919
920 if (!report.m_aBadVariableNamingFound.IsEmpty())
921 PrintFinding("badly-named variables", report.m_aBadVariableNamingFound, "use proper prefixes: m_s for ResourceName/string, m_v for vectors, NO m_p, CASED_CONSTS, etc");
922
923 if (!report.m_aBadScriptInvokerFound.IsEmpty())
924 PrintFinding("raw ScriptInvoker", report.m_aBadScriptInvokerFound, "use typed ScriptInvokerBase<> - see SCR_ScriptInvokerHelper.c for examples");
925
926 if (!report.m_aWildPrintFound.IsEmpty())
927 PrintFinding("wild Print()", report.m_aWildPrintFound, "use Print with LogLevel.XXX (or PrintFormat with level: LogLevel.XXX) to show this is not a temporary debug print");
928
929 if (!report.m_aPrintToPrintFormatFound.IsEmpty())
930 PrintFinding("string.Format Print", report.m_aPrintToPrintFormatFound, "Print(string.Format()) can be converted to PrintFormat()");
931
932 if (!report.m_aEnumsToEnumTypeFound.IsEmpty())
933 PrintFinding("enums using ParamEnumArray.FromEnum", report.m_aEnumsToEnumTypeFound, "\"enums:\" can be converted to \"enumType: EnumType\"");
934
935 if (!report.m_aNonPrefixedClassOrEnumFound.IsEmpty())
936 {
937 array<string> prefixes;
938 if (m_aAcceptedScriptPrefixes.IsEmpty())
939 prefixes = FORCED_PREFIXES;
940 else
941 prefixes = m_aAcceptedScriptPrefixes;
942
943 PrintFinding("non-prefixed class/enum", report.m_aNonPrefixedClassOrEnumFound, "classes and enums should be prefixed; see the settings to setup accepted prefixes (currently \"" + SCR_StringHelper.Join("\", \"", prefixes) + "\")");
944 }
945
946 if (!report.m_mForbiddenWordFound.IsEmpty())
947 {
948 const string description = "flagged words";
949 if (m_bShowSpellCheckFindingsDetails)
950 {
951 int count;
952 array<string> mapKeys = {}; // SCR_MapHelperT<string, ref array<int>>.GetKeys(report.m_mForbiddenWordFound);
953 foreach (string key, array<int> lineNumbers : report.m_mForbiddenWordFound)
954 {
955 mapKeys.Insert(key);
956 count += lineNumbers.Count();
957 }
958
960 "<span style='color: #DAD; font-weight: bold'>%1×</span> %2 found; see Options > Spell Check Config:",
961 count,
962 description,
963 level: LogLevel.NORMAL);
964
965 mapKeys.Sort();
966 array<int> lineNumbers;
967 foreach (string mapKey : mapKeys)
968 {
969 string tip = string.Format("\"%1\" is not an accepted word", mapKey);
970 string correction;
971 if (m_mForbiddenWords.Find(mapKey, correction))
972 {
973 correction.TrimInPlace();
974 if (correction) // !.IsEmpty()
975 tip += "; " + correction;
976 }
977
978 tip.TrimInPlace();
979 if (tip) // !.IsEmpty()
980 tip = " - " + tip;
981
982 lineNumbers = report.m_mForbiddenWordFound.Get(mapKey);
983 int lineNumbersCount = lineNumbers.Count();
984 for (int i = lineNumbersCount - 1; i >= 0; i--)
985 {
986 if (lineNumbers.Find(lineNumbers[i]) != i)
987 lineNumbers.RemoveOrdered(i);
988 }
989
991 "- <span style='color: #DAD; font-weight: bold'>%1×</span> %2 found at line(s) <span style='color: #FDA'>%3</span>%4",
992 lineNumbersCount,
993 string.Format("\"%1\"", mapKey),
994 JoinLineNumbers(lineNumbers),
995 tip,
996 level: LogLevel.NORMAL);
997 }
998 }
999 else // no details version
1000 {
1001 array<string> mapKeys = {};
1002 array<int> finalLineNumbers = {};
1003 foreach (string mapKey, array<int> lineNumbers : report.m_mForbiddenWordFound)
1004 {
1005 mapKeys.Insert(mapKey);
1006 finalLineNumbers.InsertAll(lineNumbers);
1007 }
1008
1009 mapKeys.Sort();
1010 SCR_ArrayHelperT<int>.RemoveDuplicates(finalLineNumbers);
1011
1012 PrintFinding(string.Format("%1 lines (%2 variations)", description, mapKeys.Count()), finalLineNumbers, string.Format("see Options > Spell Check Config (%1)", SCR_StringHelper.Join(", ", mapKeys)));
1013 }
1014 }
1015 }
1016 }
1017
1018 //------------------------------------------------------------------------------------------------
1023 protected int GeneralFormatting(string indentation, inout notnull array<string> pieces)
1024 {
1025 int piecesCount = pieces.Count();
1026 if (piecesCount < 1)
1027 return 0;
1028
1029 // a semicolon used to be placed after a class, not anymore
1030 if (!indentation && pieces[0].StartsWith(BRACKET_CLOSE + ";")) // !IsEmpty for perf
1031 {
1032 pieces[0] = SCR_StringHelper.ReplaceTimes(pieces[0], BRACKET_CLOSE + ";", BRACKET_CLOSE, 1);
1033 return 1;
1034 }
1035
1036 if (pieces[0].StartsWith(SCR_StringHelper.DOUBLE_SLASH)) // don't format (inline) comments
1037 return 0;
1038
1039 int replacements;
1040 array<ref array<string>> format;
1041 string toReplace;
1042 string replaceWith;
1043 bool hasChanged;
1044
1045 // line start
1046 int startPieceIndex = -1;
1047 string startPiece;
1048 foreach (int pieceIndex, string piece : pieces)
1049 {
1050 if (!SCR_StringHelper.StartsWithAny(piece, FORMAT_IGNORE))
1051 {
1052 startPiece = piece;
1053 startPieceIndex = pieceIndex;
1054 break;
1055 }
1056 }
1057
1058 if (startPiece) // !IsEmpty for perf
1059 {
1060 format = m_aGeneralFormatting_Start;
1061 for (int i, cnt = format.Count(); i < cnt; i++)
1062 {
1063 toReplace = format[i][0];
1064 replaceWith = format[i][1];
1065 hasChanged = false;
1066
1067 if (replaceWith.Contains(toReplace))
1068 {
1069 PrintFormat("%1:%2 - safety exit! %3/%4", FilePath.StripPath(__FILE__), __LINE__, toReplace, replaceWith, level: LogLevel.WARNING);
1070 continue;
1071 }
1072
1073 while (startPiece.StartsWith(toReplace))
1074 {
1075 startPiece = SCR_StringHelper.ReplaceTimes(startPiece, toReplace, replaceWith, 1);
1076 hasChanged = true;
1077 }
1078
1079 if (hasChanged)
1080 {
1081 pieces.Set(startPieceIndex, startPiece);
1082 replacements++;
1083 }
1084 }
1085
1086 // "class abc : parent" and "foreach x : y" colon spacing
1087 if (
1088 (startPiece.StartsWith("class ") || startPiece.StartsWith("enum ") || startPiece.StartsWith("foreach (")) &&
1089 startPiece.IndexOf(":") > -1
1090 )
1091 {
1092 hasChanged = false;
1093 int colonIndex = startPiece.IndexOf(":");
1094 if (startPiece[colonIndex - 1] != SCR_StringHelper.SPACE)
1095 {
1096 startPiece = SCR_StringHelper.InsertAt(startPiece, SCR_StringHelper.SPACE, colonIndex);
1097 hasChanged = true;
1098 }
1099
1100 colonIndex = startPiece.IndexOf(":");
1101 if (colonIndex < startPiece.Length() -1 && startPiece[colonIndex + 1] != SCR_StringHelper.SPACE)
1102 {
1103 startPiece = SCR_StringHelper.InsertAt(startPiece, SCR_StringHelper.SPACE, colonIndex + 1);
1104 hasChanged = true;
1105 }
1106
1107 if (hasChanged)
1108 {
1109 pieces.Set(startPieceIndex, startPiece);
1110 replacements++;
1111 }
1112 }
1113 }
1114
1115 // line middles
1116 for (int pieceIndex; pieceIndex < piecesCount; pieceIndex++)
1117 {
1118 string piece = pieces[pieceIndex];
1119
1120 if (SCR_StringHelper.StartsWithAny(piece, FORMAT_IGNORE))
1121 continue;
1122
1123 format = m_aGeneralFormatting_Middle;
1124 for (int i, cnt = format.Count(); i < cnt; i++)
1125 {
1126 toReplace = format[i][0];
1127 replaceWith = format[i][1];
1128 hasChanged = false;
1129
1130 if (replaceWith.Contains(toReplace))
1131 {
1132 PrintFormat("%1:%2 - safety exit! %3/%4", FilePath.StripPath(__FILE__), __LINE__, toReplace, replaceWith, level: LogLevel.WARNING);
1133 continue;
1134 }
1135
1136 while (piece.Contains(toReplace))
1137 {
1138 piece.Replace(toReplace, replaceWith);
1139 hasChanged = true;
1140 }
1141
1142 if (hasChanged)
1143 {
1144 pieces.Set(pieceIndex, piece);
1145 replacements++;
1146 }
1147 }
1148 }
1149
1150 // now line ends
1151 int endPieceIndex = -1;
1152 string endPiece;
1153 for (int pieceIndex = piecesCount - 1; pieceIndex >= 0; pieceIndex--)
1154 {
1155 if (!SCR_StringHelper.StartsWithAny(pieces[pieceIndex], FORMAT_IGNORE))
1156 {
1157 endPiece = pieces[pieceIndex];
1158 endPieceIndex = pieceIndex;
1159 break;
1160 }
1161 }
1162
1163 if (endPiece) // !IsEmpty for perf
1164 {
1165 format = m_aGeneralFormatting_End;
1166 for (int i, cnt = format.Count(); i < cnt; i++)
1167 {
1168 toReplace = format[i][0];
1169 replaceWith = format[i][1];
1170 hasChanged = false;
1171
1172 if (replaceWith.Contains(toReplace))
1173 {
1174 PrintFormat("%1:%2 - safety exit! %3/%4", FilePath.StripPath(__FILE__), __LINE__, toReplace, replaceWith, level: LogLevel.WARNING);
1175 continue;
1176 }
1177
1178 while (endPiece.EndsWith(toReplace))
1179 {
1180 endPiece = endPiece.Substring(0, endPiece.Length() - toReplace.Length()) + replaceWith;
1181 hasChanged = true;
1182 }
1183
1184 // start & end
1185 if (startPiece) // well, I hope a start piece exists if an end piece exists
1186 {
1187 if (startPiece.StartsWith("["))
1188 {
1189 if (endPiece.Trim().EndsWith(")];"))
1190 {
1191 int count = SCR_StringHelper.CountOccurrences(endPiece, ")];");
1192 endPiece = SCR_StringHelper.ReplaceTimes(endPiece, ")];", ")]", 1, count - 1);
1193 hasChanged = true;
1194 }
1195 }
1196 }
1197
1198 if (hasChanged)
1199 {
1200 pieces.Set(endPieceIndex, endPiece);
1201 replacements++;
1202 }
1203 }
1204 }
1205
1206 // replace a,b / a;b with a, b / a; b
1207 // replace 1/5 with 1 / 5 (+ - / *) - keep -1 but format 1-1 to 1 - 1
1208 for (int pieceIndex; pieceIndex < piecesCount; pieceIndex++)
1209 {
1210 string piece = pieces[pieceIndex];
1211 if (SCR_StringHelper.StartsWithAny(piece, FORMAT_IGNORE))
1212 continue;
1213
1214 for (int i, length = piece.Length(); i < length; i++)
1215 {
1216 // after this point is for "check after"
1217 if (i == length - 1)
1218 break;
1219
1220 string prevChar;
1221 if (i > 0)
1222 prevChar = piece[i - 1];
1223
1224 string character = piece[i];
1225
1226 string nextChar;
1227 if (i < length - 1)
1228 nextChar = piece[i + 1];
1229
1230 if ((character == "," || character == ";") && (nextChar != SCR_StringHelper.SPACE && nextChar != SCR_StringHelper.TAB && nextChar != SCR_StringHelper.DOUBLE_QUOTE)) // tab = beware of line end + tab + comment
1231 {
1232 piece = SCR_StringHelper.InsertAt(piece, SCR_StringHelper.SPACE, i + 1);
1233 i++;
1234 length++;
1235 replacements++;
1236 continue;
1237 }
1238
1239 // after this point is for "check before"
1240 if (i < 1)
1241 continue;
1242
1243 if (character == "+" || character == SCR_StringHelper.DASH || character == "*" || character == SCR_StringHelper.SLASH)
1244 {
1245 if (SCR_StringHelper.DIGITS.Contains(prevChar))
1246 {
1247 piece = SCR_StringHelper.InsertAt(piece, SCR_StringHelper.SPACE, i);
1248 length++;
1249 replacements++;
1250 continue;
1251 }
1252
1253 if (SCR_StringHelper.DIGITS.Contains(nextChar))
1254 {
1255 if (!(character == "-" && !(SCR_StringHelper.DIGITS.Contains(prevChar) || (i > 1 && SCR_StringHelper.DIGITS.Contains(piece[i - 2]))))) // exception for values like -1
1256 {
1257 piece = SCR_StringHelper.InsertAt(piece, SCR_StringHelper.SPACE, i + 1);
1258 i++;
1259 length++;
1260 replacements++;
1261 continue;
1262 }
1263 }
1264 }
1265 }
1266
1267 pieces.Set(pieceIndex, piece);
1268 }
1269
1270 return replacements;
1271 }
1272
1273// //------------------------------------------------------------------------------------------------
1274// protected void RemoveEmptyLinesInCurrentFile()
1275// {
1276// ScriptEditor scriptEditor = Workbench.GetModule(scriptEditor);
1277//
1278// // remove double empty lines
1279// int actualLine;
1280// bool isCurrentLineEmpty;
1281// bool wasPreviousLineEmpty;
1282// for (int i, cnt = scriptEditor.GetLinesCount(); i < cnt; i++)
1283// {
1284// scriptEditor.GetLineText(fullLine, i);
1285// isCurrentLineEmpty = !fullLine; // !IsEmpty for perf
1286//
1287// if (!isCurrentLineEmpty || !wasPreviousLineEmpty)
1288// {
1289// if (i != actualLine)
1290// scriptEditor.SetLineText(fullLine, actualLine);
1291//
1292// actualLine++;
1293// }
1294//
1295// wasPreviousLineEmpty = isCurrentLineEmpty;
1296// }
1297//
1298// // Print("last line with actual content = " + actualLine, LogLevel.NORMAL);
1299//
1300// // delete trailing lines
1301// for (int cnt = scriptEditor.GetLinesCount() - 1; actualLine <= cnt; cnt--)
1302// {
1303// scriptEditor.RemoveLine(cnt);
1304// }
1305//
1306// // remove top empty lines
1307// scriptEditor.GetLineText(fullLine, 0);
1308// for (int i = 0; i < 50; i++)
1309// {
1310// if (fullLine) // !IsEmpty for perf
1311// break;
1312//
1313// scriptEditor.RemoveLine(0);
1314// scriptEditor.GetLineText(fullLine, 0);
1315// }
1316//
1317// // remove bottom empty lines
1318// for (int i; i < 500; i++)
1319// {
1320// scriptEditor.RemoveLine(actualLine);
1321// }
1322// }
1323
1324 //------------------------------------------------------------------------------------------------
1327 protected bool AddFinalLineReturnToCurrentFile()
1328 {
1329 ScriptEditor scriptEditor = Workbench.GetModule(ScriptEditor);
1330 int lastLineNumber = scriptEditor.GetLinesCount() - 1;
1331 string fullLine;
1332 scriptEditor.GetLineText(fullLine, lastLineNumber);
1334 return false;
1335
1336 if (m_bDemoMode)
1337 return true;
1338
1339 // haxx
1340 scriptEditor.InsertLine(string.Empty, lastLineNumber);
1341 scriptEditor.SetLineText(fullLine, lastLineNumber);
1342 scriptEditor.SetLineText(string.Empty, lastLineNumber + 1);
1343 return true;
1344 }
1345
1346 //------------------------------------------------------------------------------------------------
1351 static void GetIndentAndLineContent(string fullLine, out string indentation, out string content)
1352 {
1353 int lineLength = fullLine.Length();
1354 if (lineLength < 1)
1355 {
1356 indentation = string.Empty;
1357 content = string.Empty;
1358 return;
1359 }
1360
1361 int firstCharIndex = -1;
1362 for (int i; i < lineLength; i++)
1363 {
1364 string character = fullLine[i];
1365 if (character != SCR_StringHelper.TAB && character != SCR_StringHelper.SPACE)
1366 {
1367 firstCharIndex = i;
1368 break;
1369 }
1370 }
1371
1372 if (firstCharIndex < 0)
1373 {
1374 indentation = fullLine;
1375 content = string.Empty;
1376 return;
1377 }
1378
1379 if (firstCharIndex == 0)
1380 {
1381 indentation = string.Empty;
1382 content = fullLine;
1383 return;
1384 }
1385
1386 indentation = fullLine.Substring(0, firstCharIndex);
1387 content = fullLine.Substring(firstCharIndex, lineLength - firstCharIndex);
1388 }
1389
1390 //------------------------------------------------------------------------------------------------
1395 protected static void GetIndentAndLineContentAsPieces(string fullLine, out string indentation, out array<string> pieces)
1396 {
1397 pieces = {};
1398
1399 string content;
1400 GetIndentAndLineContent(fullLine, indentation, content);
1401
1402 if (!content) // !IsEmpty for perf
1403 return;
1404
1405 bool isInCommentBlock;
1406 bool isInString;
1407 string currentContent;
1408 for (int i, contentLength = content.Length(); i < contentLength; i++)
1409 {
1410 string previousChar;
1411 if (i > 0)
1412 previousChar = content[i - 1];
1413
1414 string currentChar = content[i];
1415 string nextChar;
1416 if (i < contentLength - 1)
1417 nextChar = content[i + 1];
1418
1419 /*
1420 string management
1421 */
1422 if (!isInCommentBlock && currentChar == SCR_StringHelper.DOUBLE_QUOTE)
1423 {
1424 if (isInString)
1425 {
1426 if (previousChar != SCR_StringHelper.ANTISLASH) // not escaped
1427 {
1428 pieces.Insert(currentContent + currentChar);
1429 currentContent = string.Empty;
1430 isInString = false;
1431 continue;
1432 }
1433 }
1434 else
1435 {
1436 pieces.Insert(currentContent);
1437 currentContent = currentChar;
1438 isInString = true;
1439 continue;
1440 }
1441 }
1442 else
1443 /*
1444 comment management
1445 */
1446 if (!isInString && currentChar == SCR_StringHelper.SLASH)
1447 {
1448 if (isInCommentBlock)
1449 {
1450 if (previousChar == "*")
1451 {
1452 pieces.Insert(currentContent + currentChar);
1453 currentContent = string.Empty;
1454 isInCommentBlock = false;
1455 continue;
1456 }
1457 }
1458 else
1459 {
1460 if (nextChar == "*")
1461 {
1462 pieces.Insert(currentContent);
1463 currentContent = currentChar;
1464 isInCommentBlock = true;
1465 continue;
1466 }
1467 else
1468 if (nextChar == SCR_StringHelper.SLASH) // the rest is comment
1469 {
1470 if (currentContent) // !IsEmpty for perf
1471 pieces.Insert(currentContent);
1472
1473 pieces.Insert(content.Substring(i, contentLength - i));
1474 return;
1475 }
1476 }
1477 }
1478
1479 currentContent += currentChar;
1480
1481 if (i == contentLength - 1 && currentContent) // !IsEmpty for perf
1482 pieces.Insert(currentContent);
1483 }
1484 }
1485
1486 //------------------------------------------------------------------------------------------------
1490 protected bool HasBadVariableNaming(string indentation, string findingsString)
1491 {
1492 // only interested in one-tab variables :) (aka member variables)
1493 if (indentation != SCR_StringHelper.TAB)
1494 return false;
1495
1496 if (findingsString.StartsWith("[")
1497 || findingsString.StartsWith("#")
1498 || findingsString.EndsWith(")")
1499 || findingsString.EndsWith(SCR_StringHelper.COMMA)
1500 || findingsString.Contains(SCR_StringHelper.COLON))
1501 return false;
1502
1503 findingsString.Replace(SCR_StringHelper.TAB, SCR_StringHelper.SPACE);
1504 array<string> pieces = {};
1505 findingsString.Split(SCR_StringHelper.SPACE, pieces, true);
1506 int piecesCount = pieces.Count();
1507 if (piecesCount < 2)
1508 return false;
1509
1510 if (pieces[1] == "=") // that's an enum or something alike
1511 return false;
1512
1513 if (pieces.Contains("override")
1514 || pieces.Contains("void")
1515 || pieces.Contains("event")
1516 || pieces.Contains("proto")
1517 || pieces.Contains("external") // that's a method
1518// || pieces.Contains("new") // e.g ref map<string, string> m_mMap = new map<string, string>()
1519 || pieces.Contains("return")) // that's a global method's assignation
1520 return false;
1521
1522 bool isProtected = pieces.RemoveItemOrdered("protected");
1523 if (isProtected)
1524 piecesCount--;
1525
1526 bool isPrivate = pieces.RemoveItemOrdered("private");
1527 if (isPrivate)
1528 piecesCount--;
1529
1530 bool isStatic = pieces.RemoveItemOrdered("static");
1531 if (isStatic)
1532 piecesCount--;
1533
1534 bool isConst = pieces.RemoveItemOrdered("const");
1535 if (isConst)
1536 piecesCount--;
1537
1538 bool isRef = pieces.RemoveItemOrdered("ref");
1539 if (isRef)
1540 piecesCount--;
1541
1542 string type;
1543 for (int i; i < piecesCount; i++)
1544 {
1545 type += pieces[0];
1546 pieces.RemoveOrdered(0);
1548 break;
1549 }
1550
1551 string rest = SCR_StringHelper.Join(SCR_StringHelper.SPACE, pieces, false).Trim();
1552 if (!rest)
1553 return false; // can't create a false positive on e.g protected string\n m_sValue;
1554
1555 string varName;
1556 int index = SCR_StringHelper.IndexOf(rest, VARIABLE_NAME_ENDING);
1557 if (index < 0)
1558 {
1559 varName = rest.Trim(); // out of options
1560 }
1561 else
1562 {
1563 varName = rest.Substring(0, index).Trim();
1564 rest = rest.Substring(index, rest.Length() - index).Trim();
1565 if (!rest.Contains("=") && !rest.Contains(SCR_StringHelper.SEMICOLON)) // should cut most of it
1566 return false;
1567 }
1568
1569 index = varName.IndexOf("[");
1570 if (index > 0 && varName.IndexOfFrom(index, "]") > 1) // e.g m_aMyVar[5] or DEFAULT_MATRIX[4]
1571 {
1572 type = "array<x>"; // inner type is not important
1573 varName = varName.Substring(0, index); // remove []
1574 }
1575
1576 if (!SCR_StringHelper.CheckCharacters(varName, true, true, true, true))
1577 return false;
1578
1579 if (isConst) // check for UPPER_SNAKE_CASE format
1580 return varName != SCR_StringHelper.Filter(varName, SCR_StringHelper.UPPERCASE + SCR_StringHelper.DIGITS + "_");
1581
1582 // m_ or s_ variables from now on
1583
1584 if (varName.Length() < 3) // consts can be DIR or X0 etc, "normal" variables must start with the "m_" or "s_" prefix
1585 return true;
1586
1587 string expectedPrefix;
1588 if (isStatic)
1589 expectedPrefix = STATIC_PREFIX;
1590 else
1591 expectedPrefix = MEMBER_PREFIX;
1592
1593 if (!varName.StartsWith(expectedPrefix)) // myVar
1594 return true;
1595
1596 if (!SCR_StringHelper.ContainsUppercase(varName)) // m_ivalue
1597 return true;
1598
1599 if (m_mVariableTypePrefixes.Contains(type))
1600 {
1601 expectedPrefix += m_mVariableTypePrefixes.Get(type);
1602 return !varName.StartsWith(expectedPrefix);
1603 }
1604
1605 foreach (string key, string value : m_mVariableTypePrefixesStart)
1606 {
1607 if (type.StartsWith(key))
1608 {
1609 expectedPrefix += value;
1610 return !varName.StartsWith(expectedPrefix);
1611 }
1612 }
1613
1614 foreach (string key, string value : m_mVariableTypePrefixesEnd)
1615 {
1616 if (type.EndsWith(key))
1617 {
1618 expectedPrefix += value;
1619 return !varName.StartsWith(expectedPrefix);
1620 }
1621 }
1622
1623 string hungarianChar = varName[2];
1624 if (SCR_StringHelper.ContainsLowercase(hungarianChar)) // m_zSomething
1625 {
1626 if (hungarianChar == "e") // enum exception...
1627 {
1628 typename typeTypename = type.ToType();
1629 if (!typeTypename)
1630 return true;
1631
1632 for (int i; i < 8; ++i) // 1, 2, 4, 8 and that's it
1633 {
1634 if (typename.EnumToString(typeTypename, i) != "unknown")
1635 return false;
1636 }
1637
1638 return true;
1639 }
1640
1641 return true;
1642 }
1643
1644 // all good? return OK
1645
1646 return false;
1647 }
1648
1649 //------------------------------------------------------------------------------------------------
1663 protected static string JoinLineNumbers(notnull array<int> lineNumbers, int maxNumbers = LINE_NUMBER_LIMIT)
1664 {
1665 if (lineNumbers.IsEmpty())
1666 return string.Empty;
1667
1668 int count = lineNumbers.Count();
1669 if (count == 1)
1670 return lineNumbers[0].ToString();
1671
1672 int countG;
1673 bool wasSequel, isSequel;
1674 array<string> groups = {};
1675 bool hitLimit;
1676 foreach (int i, int currValue : lineNumbers)
1677 {
1678 wasSequel = isSequel;
1679 isSequel = i > 0 && currValue == lineNumbers[i - 1] + 1;
1680 if (isSequel)
1681 {
1682 if (i == count - 1)
1683 groups[countG - 1] = string.Format(LINE_NUMBER_RANGE, groups[countG - 1], currValue);
1684
1685 continue;
1686 }
1687 else
1688 {
1689 if (wasSequel)
1690 groups[countG - 1] = string.Format(LINE_NUMBER_RANGE, groups[countG - 1], lineNumbers[i - 1]);
1691
1692 groups.Insert(currValue.ToString());
1693 countG++;
1694
1695 if (maxNumbers > 0 && countG >= maxNumbers) // we have enough, leave
1696 {
1697 hitLimit = i < count - 1;
1698 break;
1699 }
1700 }
1701 }
1702
1703 string result = groups[0];
1704 for (int i = 1; i < countG - 1; i++)
1705 {
1706 result += ", " + groups[i];
1707 }
1708
1709 if (hitLimit)
1710 return result + ", " + groups[countG - 1] + ", ...";
1711
1712 if (countG > 1)
1713 result += " & " + groups[countG - 1];
1714
1715 return result;
1716 }
1717
1718 //------------------------------------------------------------------------------------------------
1728 protected void PrintFinding(string description, notnull array<int> lineNumbers, string tip = string.Empty)
1729 {
1730 description.TrimInPlace();
1731 if (lineNumbers.IsEmpty())
1732 {
1733 Print("No " + description + " found", LogLevel.NORMAL);
1734 return;
1735 }
1736
1737 tip.TrimInPlace();
1738 if (tip) // !IsEmpty for perf
1739 tip = " - " + tip;
1740
1742 "<span style='color: #DAD; font-weight: bold'>%1×</span> %2 found at line(s) <span style='color: #FDA'>%3</span>%4",
1743 lineNumbers.Count(),
1744 description,
1745 JoinLineNumbers(lineNumbers),
1746 tip,
1747 level: LogLevel.NORMAL);
1748 }
1749
1750 //------------------------------------------------------------------------------------------------
1757 protected array<int> GetFileModifiedLineNumbers(string absoluteFilePath, out bool isInRepository)
1758 {
1759 isInRepository = false;
1760
1761 if (!FileIO.FileExists(absoluteFilePath))
1762 {
1763 Print("Absolute file does not exist, skipping diff for \"" + absoluteFilePath + "\"", LogLevel.ERROR);
1764 return null;
1765 }
1766
1767 string absoluteFileDirectory = FilePath.StripFileName(absoluteFilePath);
1768 if (!absoluteFileDirectory.EndsWith(SCR_StringHelper.SLASH))
1769 absoluteFileDirectory += SCR_StringHelper.SLASH;
1770
1771 string absoluteCmdOutputFilePath = absoluteFileDirectory + DIFF_FILENAME;
1772 string commandLine = string.Format(m_sDiffCommand, absoluteFilePath, absoluteCmdOutputFilePath);
1773
1774 int errorCode = Workbench.RunCmd(commandLine, true);
1775 if (errorCode != 0)
1776 {
1777 Print("Failed to obtain diff - the file may not be VCS-added yet or the command is wrong (error code " + errorCode + ")", LogLevel.ERROR);
1778 Print("command line = " + commandLine, LogLevel.ERROR);
1779
1780 if (FileIO.FileExists(absoluteCmdOutputFilePath))
1781 {
1782 if (!FileIO.DeleteFile(absoluteCmdOutputFilePath))
1783 Print("Temp diff file could NOT be deleted", LogLevel.WARNING);
1784 }
1785
1786 return null;
1787 }
1788
1789 if (!FileIO.FileExists(absoluteCmdOutputFilePath))
1790 {
1791 Print("Temp diff file does not exist", LogLevel.ERROR);
1792 return null;
1793 }
1794
1795 isInRepository = true;
1796
1797 bool started;
1798 int lineNumber;
1799 array<int> result;
1800 FileHandle fileHandle = FileIO.OpenFile(absoluteCmdOutputFilePath, FileMode.READ);
1801 if (fileHandle)
1802 {
1803 result = {};
1804
1805 while (!fileHandle.IsEOF())
1806 {
1807 string line;
1808 fileHandle.ReadLine(line);
1809 if (line.StartsWith("@@ -"))
1810 {
1811 started = true;
1812 int plusIndex = line.IndexOf("+") + 1; // note the +1
1813 int commaIndex = line.IndexOfFrom(plusIndex, ",");
1814 string diffLineNumber = line.Substring(plusIndex, commaIndex - plusIndex);
1815 lineNumber = diffLineNumber.ToInt();
1816 continue; // lineNumber--; // because it is announcing the *next* line
1817 }
1818
1819 if (!started)
1820 continue;
1821
1822 if (line.StartsWith("-"))
1823 continue;
1824
1825 if (line.StartsWith("+"))
1826 result.Insert(lineNumber);
1827
1828 lineNumber++;
1829 }
1830
1831 fileHandle.Close();
1832 }
1833 else
1834 {
1835 Print("Cannot open the temp diff file", LogLevel.ERROR);
1836 }
1837
1838 if (!FileIO.DeleteFile(absoluteCmdOutputFilePath))
1839 Print("Cannot delete the temp diff file - " + absoluteCmdOutputFilePath, LogLevel.WARNING); // we have our result, the file will unfortunately stay
1840
1841 return result;
1842 }
1843
1844 //------------------------------------------------------------------------------------------------
1845 protected override void Configure()
1846 {
1847 if (!m_SpellCheckConfig)
1848 m_SpellCheckConfig = SCR_ConfigHelperT<SCR_BasicCodeFormatterSpellCheckConfig>.GetConfigObject(SPELLCHECK_CONFIG);
1849
1850 Workbench.ScriptDialog("Configure 'Basic Code Formatter' plugin", "Formats code, basically.", this);
1851 }
1852
1853 //------------------------------------------------------------------------------------------------
1854 [ButtonAttribute("OK", true)]
1855 protected bool ButtonOK()
1856 {
1857 return true;
1858 }
1859
1860 //------------------------------------------------------------------------------------------------
1861 [ButtonAttribute("Cancel")]
1862 protected bool ButtonCancel()
1863 {
1864 return false;
1865 }
1866
1867 //------------------------------------------------------------------------------------------------
1868 // constructor
1869 protected void SCR_BasicCodeFormatterPlugin()
1870 {
1871 // line start
1872 m_aGeneralFormatting_Start = {};
1873 m_aGeneralFormatting_Start.Insert({ "if(", "if (" });
1874 m_aGeneralFormatting_Start.Insert({ "else if(", "else if (" });
1875 m_aGeneralFormatting_Start.Insert({ "for(", "for (" });
1876 m_aGeneralFormatting_Start.Insert({ "foreach(", "foreach (" });
1877 m_aGeneralFormatting_Start.Insert({ "while(", "while (" });
1878 m_aGeneralFormatting_Start.Insert({ "switch(", "switch (" });
1879
1880 // line middle
1881 m_aGeneralFormatting_Middle = {};
1882 m_aGeneralFormatting_Middle.Insert({ SCR_StringHelper.DOUBLE_SPACE, SCR_StringHelper.SPACE }); // double spaces
1883 m_aGeneralFormatting_Middle.Insert({ ";;", ";" }); // double semi-colon (so, colon?)
1884 // m_aGeneralFormatting_Middle.Insert({ " , ", ", " });
1885 // m_aGeneralFormatting_Middle.Insert({ " ; ", "; " });
1886 m_aGeneralFormatting_Middle.Insert({ " ,", "," });
1887 m_aGeneralFormatting_Middle.Insert({ " ;", ";" });
1888 m_aGeneralFormatting_Middle.Insert({ " NULL;", " null;" });
1889 m_aGeneralFormatting_Middle.Insert({ " NULL,", " null," });
1890 m_aGeneralFormatting_Middle.Insert({ " NULL)", " null)" });
1891 m_aGeneralFormatting_Middle.Insert({ "{ }", "{}" });
1892 m_aGeneralFormatting_Middle.Insert({ "array <", "array<" });
1893 foreach (string nativeType : NATIVE_TYPES)
1894 {
1895 m_aGeneralFormatting_Middle.Insert({ "<ref " + nativeType + ">", "<" + nativeType + ">" });
1896 m_aGeneralFormatting_Middle.Insert({ "<ref " + nativeType + ",", "<" + nativeType + "," });
1897 m_aGeneralFormatting_Middle.Insert({ ", ref " + nativeType + ">", ", " + nativeType + ">" });
1898 m_aGeneralFormatting_Middle.Insert({ ", ref " + nativeType + ",", ", " + nativeType + "," });
1899 }
1900 // m_aGeneralFormatting_Middle.Insert({ "autoptr ", string.Empty }); // useless as all classes inherit from Managed and are therefore managed by ARC - WARNING - autoptr can be used as a substitute to ref!!
1901 m_aGeneralFormatting_Middle.Insert({ "( ", "(" });
1902 m_aGeneralFormatting_Middle.Insert({ " )", ")" });
1903
1904 // line end
1905 m_aGeneralFormatting_End = {};
1906 // m_aGeneralFormatting_End.Insert({ ")];", ")]" });
1907 m_aGeneralFormatting_End.Insert({ ", )]", ")]" });
1908 m_aGeneralFormatting_End.Insert({ ",)]", ")]" });
1909 // m_aGeneralFormatting_End.Insert({ "++i)", "i++)" }); // veeery tempted
1910 // m_aGeneralFormatting_End.Insert({ "--i)", "i--)" }); // to fix both :D
1911
1912 m_aForbiddenDivisions = {};
1913 array<int> forbiddenDivisors = { 2, 4, 5, 8, 10, 20, 50, 100, 1000, 10000, 100000, 1000000 };
1914 foreach (int forbiddenDivisor : forbiddenDivisors)
1915 {
1916 m_aForbiddenDivisions.Insert(" / " + forbiddenDivisor + ";");
1917 m_aForbiddenDivisions.Insert(" / " + forbiddenDivisor + ")");
1918 m_aForbiddenDivisions.Insert(" / " + forbiddenDivisor + SCR_StringHelper.SPACE);
1919 }
1920
1921 m_aPrefixLineChecks = { "class ", "enum " };
1922
1923 m_aForForEachWhileArray = { "for ", "foreach ", "while " };
1924 m_aIfForForEachWhileArray = { "if ", "for ", "foreach ", "while " };
1925 m_aNewArrayNewRefArray = { "new array<", "new ref array<" };
1926 m_aScriptInvokerArray = { "ScriptInvoker()", "ScriptInvoker<", "ScriptInvoker\t", "ScriptInvoker " };
1927 m_aEndBracketSemicolonArray = { BRACKET_CLOSE, ";" };
1928
1929 m_mVariableTypePrefixes = new map<string, string>();
1930 m_mVariableTypePrefixes.Insert("bool", "b");
1931 m_mVariableTypePrefixes.Insert("float", "f");
1932 m_mVariableTypePrefixes.Insert("int", "i");
1933 m_mVariableTypePrefixes.Insert("string", "s");
1934 m_mVariableTypePrefixes.Insert("ResourceName", "s");
1935 m_mVariableTypePrefixes.Insert("LocalizedString", "s");
1936 m_mVariableTypePrefixes.Insert("vector", "v");
1937
1938 m_mVariableTypePrefixesStart = new map<string, string>();
1939 m_mVariableTypePrefixesStart.Insert("array<", "a");
1940 m_mVariableTypePrefixesStart.Insert("map<", "m");
1941
1942 // enums - Q&D
1943 for (int i, count = SCR_StringHelper.UPPERCASE.Length(); i < count; i++)
1944 {
1945 m_mVariableTypePrefixesStart.Insert("E" + SCR_StringHelper.UPPERCASE[i], "e");
1946 m_mVariableTypePrefixesStart.Insert("SCR_E" + SCR_StringHelper.UPPERCASE[i], "e");
1947 }
1948
1949 m_mVariableTypePrefixesEnd = new map<string, string>();
1950 m_mVariableTypePrefixesEnd.Insert("Widget", "w"); // will need more advanced checks (TextWidget, etc)
1951 }
1952}
1953
1955class SCR_BasicCodeFormatterPluginFileReport
1956{
1957 string m_sRelativeFilePath;
1958 bool m_bIsPluginFile;
1959
1960 int m_iReadTime = -1;
1961 int m_iFormatTime = -1;
1962 int m_iDiffTime = -1;
1963
1964 int m_iEndSpacesRemovedTotal;
1965 int m_iSpaceInTabsReplacedTotal;
1966 int m_iFourSpacesReplacedTotal;
1967 int m_iMethodSeparatorFixedTotal;
1968 int m_iGeneralFormattingTotal;
1969
1970 int m_iLinesEdited;
1971 int m_iLinesTotal;
1972
1973 // fixes
1974 ref array<int> m_aDiffLines;
1975 ref array<int> m_aTrimmings = {}; // content is actually not used, only count... for now?
1976 ref array<int> m_aFixedIndentations = {};
1977 bool m_bHasAddedFinalLineReturn;
1978
1979 // reports
1980 ref array<int> m_aBadSeparatorFound = {};
1981 ref array<int> m_aDoubleEmptyLineFound = {};
1982 ref array<int> m_aSuperfluousEmptyLineFound = {};
1983 ref array<int> m_aMissingEmptyLineFound = {};
1984 ref array<int> m_aNewWithoutParentheseFound = {};
1985 ref array<int> m_aUselessRefFound = {};
1986 ref array<int> m_aNewArrayFound = {};
1987 ref array<int> m_aAutoKeywordFound = {};
1988 ref array<int> m_aAutoptrKeywordFound = {};
1989 ref array<int> m_aForCountCall = {};
1990 ref array<int> m_aDivideByXFound = {};
1991 ref array<int> m_aUnscopedLoopFound = {};
1992 ref array<int> m_aOneLinerFound = {};
1993 ref array<int> m_aBadVariableNamingFound = {};
1994 ref array<int> m_aBadScriptInvokerFound = {};
1995 ref array<int> m_aWildPrintFound = {};
1996 ref array<int> m_aPrintToPrintFormatFound = {};
1997 ref array<int> m_aEnumsToEnumTypeFound = {};
1998 ref array<int> m_aNonPrefixedClassOrEnumFound = {};
1999 ref map<string, ref array<int>> m_mForbiddenWordFound = new map<string, ref array<int>>();
2000
2001 //------------------------------------------------------------------------------------------------
2004 bool IsClean()
2005 {
2006 if (m_iLinesEdited != 0)
2007 return false;
2008
2009 bool areAllElementsEmpty = // max 16 '&&' elements
2010 m_aBadSeparatorFound.IsEmpty() &&
2011 m_aDoubleEmptyLineFound.IsEmpty() &&
2012 m_aSuperfluousEmptyLineFound.IsEmpty() &&
2013 m_aMissingEmptyLineFound.IsEmpty() &&
2014 m_aNewWithoutParentheseFound.IsEmpty() &&
2015 m_aUselessRefFound.IsEmpty() &&
2016 m_aNewArrayFound.IsEmpty() &&
2017 m_aAutoKeywordFound.IsEmpty() &&
2018 m_aAutoptrKeywordFound.IsEmpty() &&
2019 m_aForCountCall.IsEmpty() &&
2020 m_aDivideByXFound.IsEmpty() &&
2021 m_aUnscopedLoopFound.IsEmpty() &&
2022 m_aOneLinerFound.IsEmpty() &&
2023 m_aBadVariableNamingFound.IsEmpty() &&
2024 m_aBadScriptInvokerFound.IsEmpty() &&
2025 m_aWildPrintFound.IsEmpty();
2026
2027 if (!areAllElementsEmpty)
2028 return false;
2029
2030 areAllElementsEmpty =
2031 m_aPrintToPrintFormatFound.IsEmpty() &&
2032 m_aEnumsToEnumTypeFound.IsEmpty() &&
2033 m_aNonPrefixedClassOrEnumFound.IsEmpty() &&
2034 m_mForbiddenWordFound.IsEmpty();
2035
2036 return areAllElementsEmpty;
2037 }
2038
2039 //------------------------------------------------------------------------------------------------
2040 // constructor
2041 void SCR_BasicCodeFormatterPluginFileReport(string relativeFilePath, bool isPluginFile)
2042 {
2043 m_sRelativeFilePath = relativeFilePath;
2044 m_bIsPluginFile = isPluginFile; // check not done here in case this class gets moved away from the plugin file
2045 }
2046}
2047
2048//
2051//
2053 name: "Basic Code Formatter - forced",
2054 description: "Forces Code Formatting checking ALL lines only for the current file", // unused
2055 shortcut: "Ctrl+Alt+Shift+K",
2056 wbModules: { "ScriptEditor" },
2057 awesomeFontCode: 0xF036)]
2058class SCR_BasicCodeFormatterForcedPlugin : WorkbenchPlugin
2059{
2060 //------------------------------------------------------------------------------------------------
2061 protected override void Run()
2062 {
2063 ScriptEditor scriptEditor = Workbench.GetModule(ScriptEditor);
2064 if (!scriptEditor)
2065 return;
2066
2067 SCR_BasicCodeFormatterPlugin formatterPlugin = SCR_BasicCodeFormatterPlugin.Cast(scriptEditor.GetPlugin(SCR_BasicCodeFormatterPlugin));
2068 if (!formatterPlugin)
2069 {
2070 Print("Failed to obtain Basic Code Formatter Plugin", LogLevel.ERROR);
2071 return;
2072 }
2073
2074 formatterPlugin.RunForced();
2075 }
2076}
2077
2078#endif // WORKBENCH
class RestAPIHelper< JsonApiStruct T > content
GenerateFlowMaps WorkbenchPlugin WorkbenchPluginAttribute("Regenerate river flow-maps", "Generate and save/overwrite river flow-maps", "", "", {"WorldEditor"}, "", 0xf773)
Definition FlowmapTool.c:59
EDamageType type
SCR_DestructionSynchronizationComponentClass ScriptComponentClass int index
override void Run()
bool ButtonCancel()
bool ButtonOK()
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
override void Configure()
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
static bool WriteFileContent(string filePath, notnull array< string > lines)
static array< string > ReadFileContent(string filePath, bool printWarning=true)
static ParamEnumArray FromAddons(int titleFormat=2, int hideCoreModules=0)
static string FormatValueNameToUserFriendly(string valueName)
static string Filter(string input, string characters, bool useCharactersAsBlacklist=false)
static bool ContainsLowercase(string input)
static bool EndsWithAny(string input, notnull array< string > lineEnds)
static bool ContainsUppercase(string input)
static int CountOccurrences(string haystack, string needle, bool caseInsensitive=false)
static bool CheckCharacters(string input, bool allowLC, bool allowUC, bool allowDigits, bool allowUnderscore=false)
static int IndexOf(string input, notnull array< string > samples)
static bool StartsWithAny(string input, notnull array< string > lineStarts)
static bool ContainsAny(string input, notnull array< string > needles)
static string ReplaceTimes(string input, string sample, string replacement, int howMany=1, int skip=0)
static bool SimpleStarSearchMatches(string haystack, string needle, bool caseSensitive, bool strictMatch)
static bool IsEmptyOrWhiteSpace(string input)
static string TrimRight(string input)
static string Join(string separator, notnull array< string > pieces, bool joinEmptyEntries=true)
static string InsertAt(string input, string insertion, int insertionIndex=0)
static array< string > SearchWorkbenchFiles(array< string > fileExtensions=null, array< string > searchStrArray=null, string rootPath="", bool recursive=true)
Definition Types.c:486
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
Definition LogLevel.c:14
proto void PrintFormat(string fmt, void param1=NULL, void param2=NULL, void param3=NULL, void param4=NULL, void param5=NULL, void param6=NULL, void param7=NULL, void param8=NULL, void param9=NULL, LogLevel level=LogLevel.NORMAL)
SCR_FieldOfViewSettings Attribute
FileMode
Mode for opening file. See FileSystem::Open.
Definition FileMode.c:14
proto bool Contains(T value)