diff --git a/client/sonar-project.properties b/client/sonar-project.properties index 8271958..0f4383c 100644 --- a/client/sonar-project.properties +++ b/client/sonar-project.properties @@ -2,6 +2,7 @@ sonar.projectKey=delphilint-client sonar.projectName=DelphiLint Client sonar.projectBaseDir=. +sonar.sourceEncoding=utf-8 sonar.sources=source sonar.tests=test sonar.exclusions=**/__history/**,**/__recovery/**,**/target/** \ No newline at end of file diff --git a/client/source/DelphiLint.Analyzer.pas b/client/source/DelphiLint.Analyzer.pas index f771667..d8be315 100644 --- a/client/source/DelphiLint.Analyzer.pas +++ b/client/source/DelphiLint.Analyzer.pas @@ -235,11 +235,11 @@ function TAnalyzerImpl.FilterNonProjectFiles(const InFiles: TArray; cons constructor TAnalyzerImpl.Create; begin inherited; - FActiveIssues := TObjectDictionary>.Create; + FActiveIssues := TObjectDictionary>.Create([doOwnsValues]); FCurrentAnalysis := nil; FFileAnalyses := TDictionary.Create; FOnAnalysisStateChanged := TEventNotifier.Create; - FRules := TObjectDictionary.Create; + FRules := TObjectDictionary.Create([doOwnsValues]); FServerThread := TLintServerThread.Create; FServerThread.FreeOnTerminate := False; end; diff --git a/client/source/DelphiLint.IssueActions.pas b/client/source/DelphiLint.IssueActions.pas index 394f0ce..0ea90ef 100644 --- a/client/source/DelphiLint.IssueActions.pas +++ b/client/source/DelphiLint.IssueActions.pas @@ -292,15 +292,38 @@ procedure CalculateQuickFixReflowMetrics( procedure ReflowQuickFixEdits(TextEdits: TList; EditIndex: Integer); var - AppliedEdit: TLiveTextEdit; - I: Integer; - LinesAdded: Integer; - ColumnsAdded: Integer; - SuccessorEdit: TLiveTextEdit; AppliedLine: Integer; AppliedLineOffset: Integer; - LineAfter: Boolean; - PositionAfter: Boolean; + LinesAdded: Integer; + ColumnsAdded: Integer; + + procedure OffsetPosition(var TargetLine: Integer; var TargetOffset: Integer); + var + LineAfter: Boolean; + PositionAfter: Boolean; + begin + LineAfter := TargetLine > AppliedLine; + PositionAfter := LineAfter or ((TargetLine = AppliedLine) and (TargetOffset >= AppliedLineOffset)); + + if PositionAfter then begin + if not LineAfter then begin + // This pos starts after the applied edit on the same line, offset column + TargetOffset := TargetOffset + ColumnsAdded; + end; + + // This pos starts after the applied edit, offset line + TargetLine := TargetLine + LinesAdded; + end; + end; + +var + AppliedEdit: TLiveTextEdit; + CurrentEdit: TLiveTextEdit; + I: Integer; + StartLine: Integer; + StartLineOffset: Integer; + EndLine: Integer; + EndLineOffset: Integer; begin AppliedEdit := TextEdits[EditIndex]; AppliedLine := AppliedEdit.RelativeEndLine; @@ -313,42 +336,25 @@ procedure ReflowQuickFixEdits(TextEdits: TList; EditIndex: Intege end; for I := EditIndex + 1 to TextEdits.Count - 1 do begin - SuccessorEdit := TextEdits[I]; + CurrentEdit := TextEdits[I]; - if SuccessorEdit.RelativeEndLine < AppliedLine then begin + if CurrentEdit.RelativeEndLine < AppliedLine then begin // Higher edits don't need to be offset Continue; end; - LineAfter := SuccessorEdit.RelativeStartLine > AppliedLine; - PositionAfter := LineAfter or ( - (SuccessorEdit.RelativeStartLine = AppliedLine) and (SuccessorEdit.StartLineOffset >= AppliedLineOffset) - ); - - if PositionAfter then begin - if not LineAfter then begin - // This edit starts after the applied edit on the same line, offset column - SuccessorEdit.StartLineOffset := SuccessorEdit.StartLineOffset + ColumnsAdded; - end; - - // This edit starts after the applied edit, offset line - SuccessorEdit.RelativeStartLine := SuccessorEdit.RelativeStartLine + LinesAdded; - end; - - LineAfter := SuccessorEdit.RelativeEndLine > AppliedLine; - PositionAfter := LineAfter or ( - (SuccessorEdit.RelativeEndLine = AppliedLine) and (SuccessorEdit.EndLineOffset >= AppliedLineOffset) - ); + StartLine := CurrentEdit.RelativeStartLine; + StartLineOffset := CurrentEdit.StartLineOffset; + EndLine := CurrentEdit.RelativeEndLine; + EndLineOffset := CurrentEdit.EndLineOffset; - if PositionAfter then begin - if not LineAfter then begin - // This edit after the applied edit on the same line, offset column - SuccessorEdit.EndLineOffset := SuccessorEdit.EndLineOffset + ColumnsAdded; - end; + OffsetPosition(StartLine, StartLineOffset); + OffsetPosition(EndLine, EndLineOffset); - // This edit ends after the applied edit, offset line - SuccessorEdit.RelativeEndLine := SuccessorEdit.RelativeEndLine + LinesAdded; - end; + CurrentEdit.RelativeStartLine := StartLine; + CurrentEdit.StartLineOffset := StartLineOffset; + CurrentEdit.RelativeEndLine := EndLine; + CurrentEdit.EndLineOffset := EndLineOffset; end; end; diff --git a/client/source/DelphiLint.Properties.pas b/client/source/DelphiLint.Properties.pas index 67e8e88..f4d2456 100644 --- a/client/source/DelphiLint.Properties.pas +++ b/client/source/DelphiLint.Properties.pas @@ -72,7 +72,7 @@ TBoolPropField = class(TPropFieldBase) procedure Load(IniFile: TIniFile); override; end; - TPropertiesFile = class(TInterfacedObject) + TPropertiesFile = class(TObject) private FPath: string; FFields: TArray; diff --git a/client/source/DelphiLint.Resources.pas b/client/source/DelphiLint.Resources.pas index a8b82d8..4c2eca1 100644 --- a/client/source/DelphiLint.Resources.pas +++ b/client/source/DelphiLint.Resources.pas @@ -82,8 +82,8 @@ constructor TLintResources.Create; begin inherited; - FLoadedPngs := TObjectDictionary.Create; - FLoadedBitmaps := TObjectDictionary.Create; + FLoadedPngs := TObjectDictionary.Create([doOwnsValues]); + FLoadedBitmaps := TObjectDictionary.Create([doOwnsValues]); FLoadedStrings := TDictionary.Create; end; diff --git a/client/source/DelphiLint.Server.pas b/client/source/DelphiLint.Server.pas index 8e1d491..0778690 100644 --- a/client/source/DelphiLint.Server.pas +++ b/client/source/DelphiLint.Server.pas @@ -583,7 +583,7 @@ procedure TLintServer.OnRuleRetrieveResponse( var Pair: TJSONPair; begin - Result := TObjectDictionary.Create; + Result := TObjectDictionary.Create([doOwnsValues]); for Pair in Json do begin Result.Add(Pair.JsonString.Value, TRule.CreateFromJson(TJSONObject(Pair.JsonValue))); end; diff --git a/client/test/DelphiLintTest.MockContext.pas b/client/test/DelphiLintTest.MockContext.pas index bae069e..d25d0b0 100644 --- a/client/test/DelphiLintTest.MockContext.pas +++ b/client/test/DelphiLintTest.MockContext.pas @@ -245,7 +245,7 @@ TMockIDE = class(TObject) FActions: TList; FIDEInsightActions: TList; FEditorNotifiers: TDictionary; - FMenus: TObjectDictionary; + FMenus: TDictionary; FPluginInfos: TObjectDictionary; FPluginTitle: string; FPluginIcon: TBitmap; @@ -261,7 +261,7 @@ TMockIDE = class(TObject) property Actions: TList read FActions; property IDEInsightActions: TList read FIDEInsightActions; property EditorNotifiers: TDictionary read FEditorNotifiers; - property Menus: TObjectDictionary read FMenus; + property Menus: TDictionary read FMenus; property PluginInfos: TObjectDictionary read FPluginInfos; property PluginTitle: string read FPluginTitle write FPluginTitle; @@ -362,15 +362,18 @@ TMockIDEServices = class(THookedObject, IIDEServices) property IDE: TMockIDE read FIDE; end; + TProjectOptionsProvider = reference to function: TLintProjectOptions; + TMockLintContext = class(TInterfacedObject, ILintContext) private FAnalyzer: IAnalyzer; FIDEServices: IIDEServices; FPlugin: IPlugin; FSettings: TLintSettings; - FProjectOptionsList: TObjectDictionary; + FProjectOptionsProviders: TDictionary; FSetupValid: Boolean; FTempSettingsFile: string; + FInited: Boolean; procedure Init; procedure Deinit; @@ -387,7 +390,7 @@ TMockLintContext = class(TInterfacedObject, ILintContext) procedure MockPlugin(Plugin: IPlugin); procedure MockSettings; overload; procedure MockSettings(Settings: TLintSettings); overload; - procedure MockProjectOptions(ProjectFile: string; ProjectOptions: TLintProjectOptions); + procedure MockProjectOptions(ProjectFile: string; Provider: TProjectOptionsProvider); procedure MockInvalidSetup; procedure Reset; @@ -429,8 +432,8 @@ constructor TMockAnalyzer.Create; FFileHistories := TDictionary.Create; FFileStatuses := TDictionary.Create; - FIssues := TObjectDictionary>.Create; - FRules := TObjectDictionary.Create; + FIssues := TObjectDictionary>.Create([doOwnsValues]); + FRules := TObjectDictionary.Create([doOwnsValues]); end; //______________________________________________________________________________________________________________________ @@ -574,7 +577,7 @@ procedure TMockAnalyzer.MockFileIssue(Path: string; Line: Integer; Issue: ILiveI Path := NormalizePath(Path); FIssues.AddOrSetValue( Path, - TObjectDictionary.Create([TPair.Create(Line, Issue)])); + TDictionary.Create([TPair.Create(Line, Issue)])); end; //______________________________________________________________________________________________________________________ @@ -584,7 +587,7 @@ procedure TMockAnalyzer.MockFileIssues(Path: string; Issues: TArray) I: Integer; begin Path := NormalizePath(Path); - FIssues.AddOrSetValue(Path, TObjectDictionary.Create); + FIssues.AddOrSetValue(Path, TDictionary.Create); for I := 0 to Length(Issues) - 1 do begin FIssues[Path].AddOrSetValue(Issues[I].StartLine, Issues[I]); @@ -656,24 +659,35 @@ destructor TMockLintContext.Destroy; procedure TMockLintContext.Init; begin - FProjectOptionsList := TObjectDictionary.Create; + if FInited then begin + Exit; + end; + + FProjectOptionsProviders := TDictionary.Create; FSetupValid := True; + FInited := True; end; //______________________________________________________________________________________________________________________ procedure TMockLintContext.Deinit; begin + if not FInited then begin + Exit; + end; + FAnalyzer := nil; FPlugin := nil; FreeAndNil(FSettings); FIDEServices := nil; - FreeAndNil(FProjectOptionsList); + FreeAndNil(FProjectOptionsProviders); if FTempSettingsFile <> '' then begin TFile.Delete(FTempSettingsFile); FTempSettingsFile := ''; end; + + FInited := False; end; //______________________________________________________________________________________________________________________ @@ -714,11 +728,11 @@ function TMockLintContext.GetPlugin: IPlugin; function TMockLintContext.GetProjectOptions(ProjectFile: string): TLintProjectOptions; begin ProjectFile := NormalizePath(ProjectFile); - if not FProjectOptionsList.ContainsKey(ProjectFile) then begin + if not FProjectOptionsProviders.ContainsKey(ProjectFile) then begin raise EMockError.CreateFmt('Project options for file ''%s'' have not been mocked', [ProjectFile]); end; - Result := FProjectOptionsList[ProjectFile]; + Result := FProjectOptionsProviders[ProjectFile](); end; //______________________________________________________________________________________________________________________ @@ -762,9 +776,9 @@ procedure TMockLintContext.MockPlugin(Plugin: IPlugin); //______________________________________________________________________________________________________________________ -procedure TMockLintContext.MockProjectOptions(ProjectFile: string; ProjectOptions: TLintProjectOptions); +procedure TMockLintContext.MockProjectOptions(ProjectFile: string; Provider: TProjectOptionsProvider); begin - FProjectOptionsList.AddOrSetValue(NormalizePath(ProjectFile), ProjectOptions); + FProjectOptionsProviders.AddOrSetValue(NormalizePath(ProjectFile), Provider); end; //______________________________________________________________________________________________________________________ @@ -867,7 +881,7 @@ constructor TMockIDEServices.Create; FMacros := TDictionary.Create; FStyleColors := TDictionary.Create; FSystemColors := TDictionary.Create; - FToolBars := TObjectDictionary.Create; + FToolBars := TObjectDictionary.Create([doOwnsValues]); FModules := TList.Create; end; @@ -931,7 +945,8 @@ procedure TMockIDEServices.AddPluginBitmap(const Caption: string; Image: TBitmap const LicenseStatus: string; const Version: string); begin FIDE.PluginTitle := Caption; - FIDE.PluginIcon := Image; + FIDE.PluginIcon := TBitmap.Create; + FIDE.PluginIcon.Assign(Image); FIDE.PluginLicenseStatus := LicenseStatus; FIDE.PluginVersion := Version; FIDE.PluginRegistered := not IsUnregistered; @@ -940,10 +955,15 @@ procedure TMockIDEServices.AddPluginBitmap(const Caption: string; Image: TBitmap //______________________________________________________________________________________________________________________ function TMockIDEServices.AddPluginInfo(const Title: string; const Description: string; Image: TBitmap): Integer; +var + ImageCopy: TBitmap; begin Result := FNextId; Inc(FNextId); - FIDE.PluginInfos.Add(Result, TMockPluginInfo.Create(Title, Description, Image)); + + ImageCopy := TBitmap.Create; + ImageCopy.Assign(Image); + FIDE.PluginInfos.Add(Result, TMockPluginInfo.Create(Title, Description, ImageCopy)); end; //______________________________________________________________________________________________________________________ @@ -1137,10 +1157,12 @@ constructor TMockIDE.Create; FActions := TList.Create; FIDEInsightActions := TList.Create; FEditorNotifiers := TDictionary.Create; - FMenus := TObjectDictionary.Create; - FPluginInfos := TObjectDictionary.Create; + FMenus := TDictionary.Create; + FPluginInfos := TObjectDictionary.Create([doOwnsValues]); end; +//______________________________________________________________________________________________________________________ + destructor TMockIDE.Destroy; begin FreeAndNil(FActions); diff --git a/client/test/DelphiLintTest.Plugin.pas b/client/test/DelphiLintTest.Plugin.pas index 279a459..9ff5eea 100644 --- a/client/test/DelphiLintTest.Plugin.pas +++ b/client/test/DelphiLintTest.Plugin.pas @@ -31,8 +31,6 @@ TIDEPluginTest = class(TObject) procedure MockAllToolBars(IDEServices: TMockIDEServices); procedure BuildMockedContext(out IDEServices: TMockIDEServices); public - [Setup] - procedure Setup; [TearDown] procedure TearDown; @@ -76,13 +74,6 @@ implementation //______________________________________________________________________________________________________________________ -procedure TIDEPluginTest.Setup; -begin - MockContext.Reset; -end; - -//______________________________________________________________________________________________________________________ - procedure TIDEPluginTest.TearDown; begin MockContext.Reset; diff --git a/client/test/DelphiLintTest.Utils.pas b/client/test/DelphiLintTest.Utils.pas index 8464bfc..09c61bc 100644 --- a/client/test/DelphiLintTest.Utils.pas +++ b/client/test/DelphiLintTest.Utils.pas @@ -362,12 +362,15 @@ procedure TUtilsTest.TestTryGetProjectDirectoryWithReadOptionsOnGetsAbsoluteAnal IDEServices: TMockIDEServices; Project: TMockProject; ProjectDir: string; - ProjectOptions: TLintProjectOptions; begin MockIDEServices(IDEServices); - ProjectOptions := TLintProjectOptions.Create(CProjectOptionsFile, True); - MockContext.MockProjectOptions(CProjectFile, ProjectOptions); - ProjectOptions.AnalysisBaseDir := CProjectDirRelative; + MockContext.MockProjectOptions( + CProjectFile, + function: TLintProjectOptions + begin + Result := TLintProjectOptions.Create(CProjectOptionsFile, True); + Result.AnalysisBaseDir := CProjectDirRelative; + end); Project := TMockProject.Create; IDEServices.MockActiveProject(Project); diff --git a/companion/delphilint-vscode/src/command.ts b/companion/delphilint-vscode/src/command.ts index 3868016..5399e7e 100644 --- a/companion/delphilint-vscode/src/command.ts +++ b/companion/delphilint-vscode/src/command.ts @@ -116,6 +116,58 @@ async function doAnalyze( statusUpdate(`${issues.length} ${issueWord} found`); } +type Configuration = { + apiToken: string; + sonarHostUrl: string; + projectKey: string; + baseDir: string; + projectPropertiesPath: string; +}; + +function getDefaultConfiguration(): Configuration { + return { + apiToken: "", + sonarHostUrl: "", + projectKey: "", + baseDir: "", + projectPropertiesPath: "", + }; +} + +async function retrieveEffectiveConfiguration( + projectFile: string +): Promise { + let config = getDefaultConfiguration(); + + const projectOptions = getProjectOptions(projectFile); + if (projectOptions) { + config.baseDir = projectOptions.baseDir(); + config.projectPropertiesPath = projectOptions.projectPropertiesPath(); + if (projectOptions.connectedMode()) { + config.sonarHostUrl = projectOptions.sonarHostUrl(); + config.projectKey = projectOptions.projectKey(); + console.log(settings.getSonarTokens()); + config.apiToken = + settings.getSonarTokens()?.[projectOptions.sonarHostUrl()]?.[ + projectOptions.projectKey() + ] ?? ""; + } + } else { + config.baseDir = path.dirname(projectFile); + } + + return config; +} + +async function selectProjectFile( + statusItem: LintStatusItem +): Promise { + statusItem.setAction("Selecting project..."); + const projectChoice = await getOrPromptActiveProject(); + statusItem.setActiveProject(projectChoice); + return projectChoice || null; +} + async function analyzeFiles( serverSupplier: ServerSupplier, issueCollection: vscode.DiagnosticCollection, @@ -124,44 +176,20 @@ async function analyzeFiles( ) { inAnalysis = true; try { - statusItem.setAction("Selecting project..."); - - let apiToken = ""; - let sonarHostUrl = ""; - let projectKey = ""; - let baseDir: string | null = null; - let projectPropertiesPath = ""; - - const projectChoice = await getOrPromptActiveProject(); - statusItem.setActiveProject(projectChoice); - const projectFile = projectChoice || null; + const projectFile = await selectProjectFile(statusItem); + let config; if (projectFile) { - baseDir = path.dirname(projectFile); - - const projectOptions = getProjectOptions(projectFile); - if (projectOptions) { - baseDir = projectOptions.baseDir(); - projectPropertiesPath = projectOptions.projectPropertiesPath(); - if (projectOptions.connectedMode()) { - sonarHostUrl = projectOptions.sonarHostUrl(); - projectKey = projectOptions.projectKey(); - console.log(settings.getSonarTokens()); - apiToken = - settings.getSonarTokens()?.[projectOptions.sonarHostUrl()]?.[ - projectOptions.projectKey() - ] ?? ""; - } - } + config = await retrieveEffectiveConfiguration(projectFile); } else { - baseDir = getDefaultBaseDir(files); + config = getDefaultConfiguration(); + config.baseDir = getDefaultBaseDir(files); } statusItem.setAction("Checking files..."); - const inputFiles = constructInputFiles( files, - baseDir, + config.baseDir, projectFile ?? undefined ); if (!inputFiles) { @@ -176,22 +204,22 @@ async function analyzeFiles( statusItem.setAction("Initializing server..."); await server.initialize({ bdsPath: settings.getBdsPath(), - apiToken, + apiToken: config.apiToken, compilerVersion: settings.getCompilerVersion(), - sonarHostUrl, + sonarHostUrl: config.sonarHostUrl, sonarDelphiVersion: settings.getSonarDelphiVersion(), }); statusItem.setAction("Analyzing..."); await doAnalyze(server, issueCollection, statusItem.setAction, { - baseDir, + baseDir: config.baseDir, inputFiles: projectFile ? [...inputFiles, projectFile] : inputFiles, - projectKey, - projectPropertiesPath, - sonarHostUrl, - apiToken, + projectKey: config.projectKey, + projectPropertiesPath: config.projectPropertiesPath, + sonarHostUrl: config.sonarHostUrl, + apiToken: config.apiToken, disabledRules: - sonarHostUrl === "" && !settings.getUseDefaultRules() + config.sonarHostUrl === "" && !settings.getUseDefaultRules() ? settings.getDisabledRules() : undefined, }); diff --git a/server/delphilint-server/src/main/java/au/com/integradev/delphilint/remote/standalone/AbstractStandaloneSonarHost.java b/server/delphilint-server/src/main/java/au/com/integradev/delphilint/remote/standalone/AbstractStandaloneSonarHost.java index 3184713..485e365 100644 --- a/server/delphilint-server/src/main/java/au/com/integradev/delphilint/remote/standalone/AbstractStandaloneSonarHost.java +++ b/server/delphilint-server/src/main/java/au/com/integradev/delphilint/remote/standalone/AbstractStandaloneSonarHost.java @@ -110,8 +110,6 @@ public SonarCharacteristics getCharacteristics() { return SonarCharacteristics.latest(); } - public abstract String getName(); - @Override public Map getRuleNamesByRuleKey() { return getRules().stream().collect(Collectors.toMap(RemoteRule::getKey, RemoteRule::getName)); diff --git a/server/delphilint-server/src/main/java/au/com/integradev/delphilint/server/message/ResponseAnalyzeResult.java b/server/delphilint-server/src/main/java/au/com/integradev/delphilint/server/message/ResponseAnalyzeResult.java index e4f6a2e..6518685 100644 --- a/server/delphilint-server/src/main/java/au/com/integradev/delphilint/server/message/ResponseAnalyzeResult.java +++ b/server/delphilint-server/src/main/java/au/com/integradev/delphilint/server/message/ResponseAnalyzeResult.java @@ -46,15 +46,14 @@ public static ResponseAnalyzeResult fromIssueSet(Collection delphiI Set issues = delphiIssues.stream() .map( - delphiIssue -> { - return new IssueData( - delphiIssue.getRuleKey(), - delphiIssue.getMessage(), - delphiIssue.getFile(), - transformRange(delphiIssue.getTextRange()), - transformMetadata(delphiIssue.getMetadata()), - transformQuickFixes(delphiIssue.getQuickFixes())); - }) + delphiIssue -> + new IssueData( + delphiIssue.getRuleKey(), + delphiIssue.getMessage(), + delphiIssue.getFile(), + transformRange(delphiIssue.getTextRange()), + transformMetadata(delphiIssue.getMetadata()), + transformQuickFixes(delphiIssue.getQuickFixes()))) .collect(Collectors.toSet()); return new ResponseAnalyzeResult(issues);