├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── main.yml │ └── pre-commit.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── manual.md ├── style.md └── vsix_installation.md ├── logo ├── 128.png ├── 16.png ├── 256.png ├── 32.png ├── 64.png ├── 96.png ├── TcBlack_icon.ico ├── TcBlack_icon.svg ├── TcBlack_icon_square.svg ├── TcBlack_logo.svg ├── TcBlack_logo_big.png └── TcBlack_logo_small.png ├── src ├── TcBlack.ruleset ├── TcBlack.sln ├── TcBlack │ ├── FormatStructuredText.cs │ ├── FormatStructuredTextPackage.cs │ ├── FormatStructuredTextPackage.vsct │ ├── LICENSE.txt │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── References │ │ └── TCatSysManagerLib.dll │ ├── TcBlack.csproj │ ├── TcBlack_icon.ico │ ├── VSPackage.resx │ ├── packages.config │ ├── source.extension.vsixmanifest │ └── stylesheet.css ├── TcBlackCLI │ ├── App.config │ ├── Backup.cs │ ├── BuildTwinCatProject.bat │ ├── Options.cs │ ├── Program.cs │ ├── ProjectBuildFailedException.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── TcBlackCLI.csproj │ ├── TcBlack_icon.ico │ ├── TcPou.cs │ ├── TcProjectBuilder.cs │ └── packages.config ├── TcBlackCLITests │ ├── BackupTestData │ │ ├── BackupFileAlreadyExists.txt │ │ └── InitializeObjectCreatesBackupFile.txt │ ├── BackupTests.cs │ ├── MockTcProjectBuilder.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── TcBlackCLITests.csproj │ ├── TcPouTestData │ │ ├── FB_ExpectedComplex.TcPOU │ │ ├── FB_ExpectedOverrideIndentation.TcPOU │ │ ├── FB_ExpectedOverrideLineEnding.TcPOU │ │ ├── FB_ExpectedOverrideLineEndingAndIndentation.TcPOU │ │ ├── FB_ExpectedSimple.TcPOU │ │ ├── FB_ExpectedTabAndUnixLineEnd.TcPOU │ │ ├── FB_ExpectedWithEmptyVars.TcPOU │ │ ├── FB_ExpectedWithPropertiesAndMethods.TcPOU │ │ ├── FB_InputComplex.TcPOU │ │ ├── FB_InputOverrideIndentation.TcPOU │ │ ├── FB_InputOverrideLineEnding.TcPOU │ │ ├── FB_InputOverrideLineEndingAndIndentation.TcPOU │ │ ├── FB_InputSimple.TcPOU │ │ ├── FB_InputTabAndUnixLineEnd.TcPOU │ │ ├── FB_InputWithEmptyVars.TcPOU │ │ └── FB_InputWithPropertiesAndMethods.TcPOU │ ├── TcPouTests.cs │ ├── TcProjectBuildTestData │ │ ├── failedBuildWithExtraTextBelow.log │ │ ├── firstBuildOkSecondBuildFailed.log │ │ └── succesfulBuild.log │ ├── TcProjectBuilderTests.cs │ └── packages.config ├── TcBlackCore │ ├── CodeLineBase.cs │ ├── CompositeCode.cs │ ├── EmptyLine.cs │ ├── Globals.cs │ ├── Keywords.cs │ ├── ObjectDefinition.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── StringExtensions.cs │ ├── TcBlackCore.csproj │ ├── TcBlack_icon.ico │ ├── TcDeclaration.cs │ ├── UnknownCodeType.cs │ ├── VariableBlockEnd.cs │ ├── VariableBlockStart.cs │ └── VariableDeclaration.cs ├── TcBlackCoreTests │ ├── CompositeCodeTests.cs │ ├── EmptyLineTests.cs │ ├── ObjectDefinitionTests.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── TcBlackCoreTests.csproj │ ├── UnknownCodeTypeTests.cs │ ├── VarBlockEndTests.cs │ ├── VarBlockStartTests.cs │ ├── VariableDeclarationTests.cs │ └── packages.config ├── TcBlackTests.ruleset └── fileForUnitTest.slnx ├── tcblack_extension.gif └── twincat ├── BrokenProjectForUnitTests ├── BrokenProjectForUnitTests.tspproj └── PLC2 │ ├── PLC2.plcproj │ ├── POUs │ └── MAIN.TcPOU │ └── PlcTask.TcTTO ├── ShowcaseProject ├── PLC3 │ ├── PLC3.plcproj │ ├── POUs │ │ ├── FB_Base.TcPOU │ │ ├── FB_Child.TcPOU │ │ ├── I_Interface.TcIO │ │ ├── I_Interface2.TcIO │ │ └── MAIN.TcPOU │ └── PlcTask.TcTTO └── ShowcaseProject.tsproj ├── TcBlack_TwinCAT.sln └── WorkingProjectForUnitTests ├── PLC ├── PLC.plcproj ├── POUs │ ├── MAIN.TcPOU │ └── Sum.TcPOU ├── PlcTask.TcTTO └── _CompileInfo │ └── E7C52539-BBF0-7365-BEC4-14FF9FECC46D.compileinfo └── WorkingProjectForUnitTests.tspproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | *.jpg binary 44 | *.png binary 45 | *.gif binary 46 | *.svg binary 47 | *.ico binary 48 | 49 | ############################################################################### 50 | # diff behavior for common document formats 51 | # 52 | # Convert binary document formats to text before diffing them. This feature 53 | # is only available from the command line. Turn it on by uncommenting the 54 | # entries below. 55 | ############################################################################### 56 | #*.doc diff=astextplain 57 | #*.DOC diff=astextplain 58 | #*.docx diff=astextplain 59 | #*.DOCX diff=astextplain 60 | #*.dot diff=astextplain 61 | #*.DOT diff=astextplain 62 | #*.pdf diff=astextplain 63 | #*.PDF diff=astextplain 64 | #*.rtf diff=astextplain 65 | #*.RTF diff=astextplain 66 | 67 | *.dll binary -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: roald87 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 13 | jobs: 14 | build: 15 | runs-on: windows-latest 16 | 17 | # Steps represent a sequence of tasks that will be executed as part of the job 18 | steps: 19 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 20 | - name: Check out code 21 | uses: actions/checkout@v2 22 | 23 | - name: Setup MSBuild 24 | uses: microsoft/setup-msbuild@v1.0.2 25 | 26 | - name: Setup NuGet 27 | uses: NuGet/setup-nuget@v1.0.5 28 | 29 | - name: Restore NuGet packages 30 | run: nuget restore src/TcBlack.sln 31 | 32 | - name: Build all projects 33 | run: | 34 | msbuild src\TcBlack.sln -t:Build -p:Configuration=Release -p:Platform="Any CPU" -p:TreatWarningsAsErrors=true 35 | 36 | - name: Setup VSTest Path 37 | uses: darenm/Setup-VSTest@v1 38 | 39 | - name: Run unit tests 40 | run: | 41 | vstest.console.exe src\TcBlackCLITests\bin\Release\TcBlackCLITests.dll src\TcBlackCoreTests\bin\Release\TcBlackCoreTests.dll 42 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: [main, test-me-*] 5 | 6 | jobs: 7 | main: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-python@v4 12 | with: 13 | python-version: 3.x 14 | - uses: actions/setup-dotnet@v3 15 | with: 16 | dotnet-version: "7.x" 17 | - run: | 18 | dotnet tool install --global csharpier 19 | dotnet tool install --global fantomas 20 | - uses: pre-commit/action@v3.0.0 21 | - uses: pre-commit-ci/lite-action@v1.0.0 22 | if: always() 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gitignore template for TwinCAT3 2 | # website: https://www.beckhoff.com/twincat3/ 3 | # 4 | # Recommended: VisualStudio.gitignore 5 | 6 | # TwinCAT files 7 | *.tpy 8 | *.tclrs 9 | *.compiled-library 10 | *.compileinfo 11 | *.tmc 12 | *.tmcRefac 13 | *.library 14 | *.project.~u 15 | *.bak 16 | LineIDs.dbg 17 | _Boot/ 18 | _Libraries/ 19 | 20 | ## Ignore Visual Studio temporary files, build results, and 21 | ## files generated by popular Visual Studio add-ons. 22 | ## 23 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 24 | 25 | # User-specific files 26 | *.rsuser 27 | *.suo 28 | *.user 29 | *.userosscache 30 | *.sln.docstates 31 | 32 | # User-specific files (MonoDevelop/Xamarin Studio) 33 | *.userprefs 34 | 35 | # Mono auto generated files 36 | mono_crash.* 37 | 38 | # Build results 39 | [Dd]ebug/ 40 | [Dd]ebugPublic/ 41 | [Rr]elease/ 42 | [Rr]eleases/ 43 | x64/ 44 | x86/ 45 | [Aa][Rr][Mm]/ 46 | [Aa][Rr][Mm]64/ 47 | bld/ 48 | [Bb]in/ 49 | [Oo]bj/ 50 | [Ll]og/ 51 | [Ll]ogs/ 52 | 53 | # Visual Studio 2015/2017 cache/options directory 54 | .vs/ 55 | # Uncomment if you have tasks that create the project's static files in wwwroot 56 | #wwwroot/ 57 | 58 | # Visual Studio 2017 auto generated files 59 | Generated\ Files/ 60 | 61 | # MSTest test Results 62 | [Tt]est[Rr]esult*/ 63 | [Bb]uild[Ll]og.* 64 | 65 | # NUnit 66 | *.VisualState.xml 67 | TestResult.xml 68 | nunit-*.xml 69 | 70 | # Build Results of an ATL Project 71 | [Dd]ebugPS/ 72 | [Rr]eleasePS/ 73 | dlldata.c 74 | 75 | # Benchmark Results 76 | BenchmarkDotNet.Artifacts/ 77 | 78 | # .NET Core 79 | project.lock.json 80 | project.fragment.lock.json 81 | artifacts/ 82 | 83 | # StyleCop 84 | StyleCopReport.xml 85 | 86 | # Files built by Visual Studio 87 | *_i.c 88 | *_p.c 89 | *_h.h 90 | *.ilk 91 | *.meta 92 | *.obj 93 | *.iobj 94 | *.pch 95 | *.pdb 96 | *.ipdb 97 | *.pgc 98 | *.pgd 99 | *.rsp 100 | *.sbr 101 | *.tli 102 | *.tlh 103 | *.tmp 104 | *.tmp_proj 105 | *_wpftmp.csproj 106 | *.log 107 | *.vspscc 108 | *.vssscc 109 | .builds 110 | *.pidb 111 | *.svclog 112 | *.scc 113 | 114 | # Chutzpah Test files 115 | _Chutzpah* 116 | 117 | # Visual C++ cache files 118 | ipch/ 119 | *.aps 120 | *.ncb 121 | *.opendb 122 | *.opensdf 123 | *.sdf 124 | *.cachefile 125 | *.VC.db 126 | *.VC.VC.opendb 127 | 128 | # Visual Studio profiler 129 | *.psess 130 | *.vsp 131 | *.vspx 132 | *.sap 133 | 134 | # Visual Studio Trace Files 135 | *.e2e 136 | 137 | # TFS 2012 Local Workspace 138 | $tf/ 139 | 140 | # Guidance Automation Toolkit 141 | *.gpState 142 | 143 | # ReSharper is a .NET coding add-in 144 | _ReSharper*/ 145 | *.[Rr]e[Ss]harper 146 | *.DotSettings.user 147 | 148 | # TeamCity is a build add-in 149 | _TeamCity* 150 | 151 | # DotCover is a Code Coverage Tool 152 | *.dotCover 153 | 154 | # AxoCover is a Code Coverage Tool 155 | .axoCover/* 156 | !.axoCover/settings.json 157 | 158 | # Visual Studio code coverage results 159 | *.coverage 160 | *.coveragexml 161 | 162 | # NCrunch 163 | _NCrunch_* 164 | .*crunch*.local.xml 165 | nCrunchTemp_* 166 | 167 | # MightyMoose 168 | *.mm.* 169 | AutoTest.Net/ 170 | 171 | # Web workbench (sass) 172 | .sass-cache/ 173 | 174 | # Installshield output folder 175 | [Ee]xpress/ 176 | 177 | # DocProject is a documentation generator add-in 178 | DocProject/buildhelp/ 179 | DocProject/Help/*.HxT 180 | DocProject/Help/*.HxC 181 | DocProject/Help/*.hhc 182 | DocProject/Help/*.hhk 183 | DocProject/Help/*.hhp 184 | DocProject/Help/Html2 185 | DocProject/Help/html 186 | 187 | # Click-Once directory 188 | publish/ 189 | 190 | # Publish Web Output 191 | *.[Pp]ublish.xml 192 | *.azurePubxml 193 | # Note: Comment the next line if you want to checkin your web deploy settings, 194 | # but database connection strings (with potential passwords) will be unencrypted 195 | *.pubxml 196 | *.publishproj 197 | 198 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 199 | # checkin your Azure Web App publish settings, but sensitive information contained 200 | # in these scripts will be unencrypted 201 | PublishScripts/ 202 | 203 | # NuGet Packages 204 | *.nupkg 205 | # NuGet Symbol Packages 206 | *.snupkg 207 | # The packages folder can be ignored because of Package Restore 208 | **/[Pp]ackages/* 209 | # except build/, which is used as an MSBuild target. 210 | !**/[Pp]ackages/build/ 211 | # Uncomment if necessary however generally it will be regenerated when needed 212 | #!**/[Pp]ackages/repositories.config 213 | # NuGet v3's project.json files produces more ignorable files 214 | *.nuget.props 215 | *.nuget.targets 216 | 217 | # Microsoft Azure Build Output 218 | csx/ 219 | *.build.csdef 220 | 221 | # Microsoft Azure Emulator 222 | ecf/ 223 | rcf/ 224 | 225 | # Windows Store app package directories and files 226 | AppPackages/ 227 | BundleArtifacts/ 228 | Package.StoreAssociation.xml 229 | _pkginfo.txt 230 | *.appx 231 | *.appxbundle 232 | *.appxupload 233 | 234 | # Visual Studio cache files 235 | # files ending in .cache can be ignored 236 | *.[Cc]ache 237 | # but keep track of directories ending in .cache 238 | !?*.[Cc]ache/ 239 | 240 | # Others 241 | ClientBin/ 242 | ~$* 243 | *~ 244 | *.dbmdl 245 | *.dbproj.schemaview 246 | *.jfm 247 | *.pfx 248 | *.publishsettings 249 | orleans.codegen.cs 250 | 251 | # Including strong name files can present a security risk 252 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 253 | #*.snk 254 | 255 | # Since there are multiple workflows, uncomment next line to ignore bower_components 256 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 257 | #bower_components/ 258 | 259 | # RIA/Silverlight projects 260 | Generated_Code/ 261 | 262 | # Backup & report files from converting an old project file 263 | # to a newer Visual Studio version. Backup files are not needed, 264 | # because we have git ;-) 265 | _UpgradeReport_Files/ 266 | UpgradeLog*.XML 267 | UpgradeLog*.htm 268 | ServiceFabricBackup/ 269 | *.rptproj.bak 270 | 271 | # SQL Server files 272 | *.mdf 273 | *.ldf 274 | *.ndf 275 | 276 | # Business Intelligence projects 277 | *.rdl.data 278 | *.bim.layout 279 | *.bim_*.settings 280 | *.rptproj.rsuser 281 | *- [Bb]ackup.rdl 282 | *- [Bb]ackup ([0-9]).rdl 283 | *- [Bb]ackup ([0-9][0-9]).rdl 284 | 285 | # Microsoft Fakes 286 | FakesAssemblies/ 287 | 288 | # GhostDoc plugin setting file 289 | *.GhostDoc.xml 290 | 291 | # Node.js Tools for Visual Studio 292 | .ntvs_analysis.dat 293 | node_modules/ 294 | 295 | # Visual Studio 6 build log 296 | *.plg 297 | 298 | # Visual Studio 6 workspace options file 299 | *.opt 300 | 301 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 302 | *.vbw 303 | 304 | # Visual Studio LightSwitch build output 305 | **/*.HTMLClient/GeneratedArtifacts 306 | **/*.DesktopClient/GeneratedArtifacts 307 | **/*.DesktopClient/ModelManifest.xml 308 | **/*.Server/GeneratedArtifacts 309 | **/*.Server/ModelManifest.xml 310 | _Pvt_Extensions 311 | 312 | # Paket dependency manager 313 | .paket/paket.exe 314 | paket-files/ 315 | 316 | # FAKE - F# Make 317 | .fake/ 318 | 319 | # CodeRush personal settings 320 | .cr/personal 321 | 322 | # Python Tools for Visual Studio (PTVS) 323 | __pycache__/ 324 | *.pyc 325 | 326 | # Cake - Uncomment if you are using it 327 | # tools/** 328 | # !tools/packages.config 329 | 330 | # Tabs Studio 331 | *.tss 332 | 333 | # Telerik's JustMock configuration file 334 | *.jmconfig 335 | 336 | # BizTalk build output 337 | *.btp.cs 338 | *.btm.cs 339 | *.odx.cs 340 | *.xsd.cs 341 | 342 | # OpenCover UI analysis results 343 | OpenCover/ 344 | 345 | # Azure Stream Analytics local run output 346 | ASALocalRun/ 347 | 348 | # MSBuild Binary and Structured Log 349 | *.binlog 350 | 351 | # NVidia Nsight GPU debugger configuration file 352 | *.nvuser 353 | 354 | # MFractors (Xamarin productivity tool) working folder 355 | .mfractor/ 356 | 357 | # Local History for Visual Studio 358 | .localhistory/ 359 | 360 | # BeatPulse healthcheck temp database 361 | healthchecksdb 362 | 363 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 364 | MigrationBackup/ 365 | 366 | # Ionide (cross platform F# VS Code tools) working folder 367 | .ionide/ 368 | 369 | !failedBuildWithExtraTextBelow.log 370 | !firstBuildOkSecondBuildFailed.log 371 | !succesfulBuild.log 372 | !E7C52539-BBF0-7365-BEC4-14FF9FECC46D.compileinfo 373 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: '.*\.(resx|Designer.cs|xti|tcscopex|tsproj|csproj|vsixmanifest|config|TcPOU)' 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.3.0 5 | hooks: 6 | # Removes trailing white spaces 7 | - id: trailing-whitespace 8 | # Checks yaml files for parseable syntax 9 | - id: check-yaml 10 | # Prevents git from committing large files 11 | - id: check-added-large-files 12 | - repo: local 13 | hooks: 14 | - id: CSharpier 15 | name: Format C# files 16 | entry: dotnet csharpier 17 | language: system 18 | files: '.*\.(cs$)' 19 | - repo: https://github.com/psf/black 20 | rev: 22.6.0 21 | hooks: 22 | - id: black 23 | - repo: https://gitlab.com/rruiter87/pre-commit-hooks 24 | rev: v1.3.0 25 | hooks: 26 | - id: check-poetry 27 | - repo: https://github.com/PyCQA/flake8 28 | rev: 4.0.1 29 | hooks: 30 | - id: flake8 31 | exclude: ^templates/ 32 | - repo: https://github.com/pycqa/isort 33 | rev: 5.10.1 34 | hooks: 35 | - id: isort 36 | args: ["--profile", "black", "--filter-files"] 37 | - repo: https://github.com/pre-commit/mirrors-prettier 38 | rev: v2.7.1 39 | hooks: 40 | - id: prettier 41 | exclude_types: ["markdown", "svg"] 42 | additional_dependencies: 43 | - prettier@2.6.2 44 | - "@prettier/plugin-xml@0.12.0" 45 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to TcBlack 2 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 3 | 4 | - Reporting a bug 5 | - Discussing the current state of the code 6 | - Submitting a fix 7 | - Proposing new features 8 | - Becoming a maintainer 9 | 10 | You can also work on an open issue. Issues which are not assigned to anyone, are up for grabs. 11 | 12 | ## We Develop with GitHub 13 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. 14 | 15 | ## We Use [GitHub Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests 16 | Pull requests are the best way to propose changes to the codebase (we use [GitHub Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests: 17 | 18 | 1. Fork the repo and create your branch from `master`. 19 | 2. Make the changes in your forked branch. 20 | 3. If you've added new functionality, it's necessary to add tests. For this there is a separate test project called [TcBlackTests](https://github.com/Roald87/TcBlack/tree/master/src/TcBlackTests). In that project there is a separate test suite defined for each class in TcBlack. **No new functionality will be accepted without any proper tests**. 21 | 3. Ensure the test suite passes. 22 | 4. Issue that pull request! 23 | 24 | ## Any contributions you make will be under the MIT Software License 25 | In short, when you submit code changes, your submissions are understood to be under the 26 | same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 27 | 28 | ## Report bugs/ideas using GitHub's [issues](https://github.com/Roald87/TcBlack/issues) 29 | We use GitHub issues to track public bugs and collect ideas. Report a bug or suggest an idea by [opening a new issue](https://github.com/Roald87/TcBlack/issues/new); it's that easy! 30 | 31 | ## Write bug reports and ideas with detail, background, and sample code 32 | 33 | **Great bug reports/ideas** tend to have: 34 | 35 | - A quick summary and/or background. 36 | - Steps to reproduce: 37 | - Be specific! 38 | - Give sample code if you can. 39 | - What you expected would happen. 40 | - What actually happens. 41 | - Notes (possibly including why you think this might be happening, or stuff you tried 42 | that didn't work). 43 | 44 | ## Build environment 45 | * Make sure to edit the project with the same version of Visual Studio as the master 46 | branch. TcBlack has been developed using Visual Studio 2017. You can download 47 | [VS2017 Community edition](https://visualstudio.microsoft.com/vs/older-downloads/) for free. 48 | * Although most of the development will take place in C#, it is good to use the same 49 | TwinCAT version if you're making changes to the TwinCAT projects. TcBlack currently 50 | uses **TwinCAT 4024.7**. 51 | 52 | ## Use a Consistent Coding Style 53 | * In order to keep the code readable, a maximum line length of 88 characters is used. This is the same in [Black](https://github.com/psf/black) for Python. You can add a [Guideline](https://marketplace.visualstudio.com/items?itemName=PaulHarrington.EditorGuidelines) to the Visual Studio to help you here. 54 | 55 | Furthermore use the following TwinCAT editor settings: 56 | 57 | * Make sure that your TwinCAT development environment uses spaces instead of tabs. The default behavior of the TwinCAT development environment is to use tabs so it needs to be changed. The option can be found under **Tools → Options → TwinCAT → PLC Environment → Text editor**. Here you want to de-select **Keep tabs**. See also [this guide](https://alltwincat.com/2017/04/14/replace-tabs-with-whitespaces/). 58 | * Make sure to set your TwinCAT development environment to use Separate LineIDs. This 59 | is available in the TwinCAT XAE under **Tools → Options → TwinCAT → Write options → Separate LineIDs** (set this to TRUE, more information is available [here](https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_userinterface/18014403202147467.html&id=)). 60 | 61 | ## Testing 62 | If you've implemented a new feature, you can try it by formatting or adding a file to the ShowcaseProject. Please only commit pre-formatted versions of these files, otherwise others can't use it. To show the new feature you can update the README's Current state section. 63 | 64 | ## License 65 | By contributing, you agree that your contributions will be licensed under its MIT License. 66 | 67 | ## References 68 | This document was adapted from TcUnits's excellent [contribution guidelines](https://github.com/tcunit/TcUnit/blob/master/CONTRIBUTING.md). 69 | 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Roald87 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Official TcBlack logo](logo/TcBlack_logo_small.png) 2 | 3 | # TcBlack: TwinCAT code formatter 4 | 5 | Opinionated code formatter for TwinCAT. Currently in the **alpha state**. Use at your **own risk** and only with files which are under source control. 6 | 7 | _TcBlack_ is available as a command line tool ([TcBlackCLI](https://github.com/Roald87/TcBlack/releases/tag/v0.2.0)) as well as a visual studio extension ([TcBlackExtension](https://github.com/Roald87/TcBlack/releases/tag/TcBlackExtension_v0.1.0)). 8 | 9 | ## Current state 10 | 11 | `FB_Child` from ShowcaseProject formatted using the _TcBlackExtension_ for Visual Studio. 12 | 13 | ![tcblack_extension](tcblack_extension.gif) 14 | 15 | ## _TcBlackCLI_ usage 16 | 17 | 1. [Download](https://github.com/Roald87/TcBlack/releases/latest) the latest release. 18 | 1. Open the windows command prompt and navigate to the folder containing `TcBlack.exe`. 19 | 1. Reformat one or more file by giving their full path names: 20 | 21 | ``` 22 | > TcBlack --safe --file C:\Full\Path\To\Filename.TcPOU C:\Full\Path\To\AnotherFilename.TcPOU 23 | ``` 24 | 25 | or using the short version and format a single file: 26 | 27 | ``` 28 | > TcBlack --safe -f C:\Full\Path\To\Filename.TcPOU 29 | ``` 30 | 31 | or format a whole project at once and replace all indentation by a two spaces: 32 | 33 | ``` 34 | > TcBlack --safe -f C:\Full\Path\To\Project.plcproj --indentation " " 35 | ``` 36 | 37 | For more info enter `> TcBlack --help` in the command prompt or check the 38 | [manual](docs/manual.md). 39 | 40 | ## Installing the extension 41 | 42 | To install the VSIX extension in Visual Studio or TcXaeShell please see the [installation guide](docs/vsix_installation.md). 43 | 44 | ## Idea 45 | 46 | Change 47 | 48 | ``` 49 | FUNCTION_BLOCK FB_Child EXTENDS FB_Base IMPLEMENTS I_Interface,I_Interface2 50 | 51 | VAR_INPUT 52 | END_VAR 53 | VAR 54 | 55 | SomeText: STRING; 56 | Counter : DINT:= 1 ; 57 | Result : DINT :=2; 58 | 59 | 60 | Base:FB_Base; 61 | END_VAR 62 | =================================== 63 | SomeText:= 'Current counts'; 64 | 65 | IF Conditions[1] AND Conditions[2] AND Conditions[3] AND Conditions[4] AND Conditions[5]AND Conditions[6] THEN 66 | Counter :=Counter+ 1; 67 | 68 | IF Counter > 2 THEN 69 | Counter := Counter + 5 ; 70 | END_IF 71 | END_IF 72 | 73 | Base(Variable1:=2, Variable2:=3 , Variable3:= 5,Sentence:='Entropy is a real bitch.', Conditions :=Conditions); 74 | 75 | 76 | AddTwoInts( Variable1 :=4, 77 | Variable2:=4); 78 | ``` 79 | 80 | Into 81 | 82 | ``` 83 | FUNCTION_BLOCK FB_Child 84 | EXTENDS FB_Base 85 | IMPLEMENTS I_Interface, I_Interface2 86 | VAR 87 | SomeText : STRING; 88 | Counter : DINT := 1; 89 | Result : DINT := 2; 90 | 91 | Base : FB_Base; 92 | END_VAR 93 | 94 | =================================== 95 | SomeText := 'Current counts'; 96 | 97 | IF 98 | Conditions[1] 99 | AND Conditions[2] 100 | AND Conditions[3] 101 | AND Conditions[4] 102 | AND Conditions[5] 103 | AND Conditions[6] 104 | THEN 105 | Counter := Counter + 1; 106 | 107 | IF Counter > 2 THEN 108 | Counter := Counter + 5 ; 109 | END_IF 110 | END_IF 111 | 112 | Base( 113 | Variable1:=2, 114 | Variable2:=3 , 115 | Variable3:=5, 116 | Sentence:='Entropy is a real bitch.', 117 | Conditions:=Conditions 118 | ); 119 | 120 | AddTwoInts(Variable1:=4, Variable2:=4); 121 | 122 | ``` 123 | 124 | ## Why 125 | 126 | Get a consistent style across your project, without having to go through all the code. Focus on the logic and structure of the code, not the formatting. 127 | 128 | ## How 129 | 130 | By making a command line tool which can be either used manually on individual files, a whole project or added as a pre-hook commit which automatically reformats before making a commit. 131 | 132 | ## Style 133 | 134 | Follow the same style rules as [Black](https://github.com/psf/black/) for Python (where applicable). Why try to reinvent the wheel, when Black offers a popular rule base which has been tested and tried? For more info see the [style guide](docs/style.md). 135 | 136 | ## _TcBlackCLI_ implementation 137 | 138 | There are two modes for the _TcBlackCLI_. A safe mode which checks if the code did not undergo unwanted changes after reformatting. The non-safe mode is faster, but it could be that there were unwanted changes to the code. _TcBlackExtension_ always operates in the non-safe mode. 139 | 140 | The safe mode builds the project before and after formatting. It then compares the generated number (a sort of checksum?) which is used as the name of the `*.compileinfo` file. This file is generated in the _\_CompileInfo_ folder of a project each time it is build. 141 | 142 | The number doesn't change when you alter whitespaces, add/change comments or add brackets around a long if statement. Only if the actual code changes then the number also changes. For example, if you add a variable, add a line of code or change the order of variables. 143 | 144 | ## Contributing 145 | 146 | You're more then welcome to help if you'd like! See the [contributing guidelines](CONTRIBUTING.md) for more info. 147 | -------------------------------------------------------------------------------- /docs/manual.md: -------------------------------------------------------------------------------- 1 | # Manual 2 | 3 | Below are all the different command line options available for _TcBlack_. In order to run these commands: open a windows command prompt and navigate to the folder where `TcBlack.exe` is located. Then enter one of the commands, or combine some options. 4 | 5 | ## Help 6 | 7 | `--help` 8 | 9 | Shows the different commands and options which can be used with _TcBlack_. 10 | 11 | ### Example 12 | ``` 13 | TcBlack --help 14 | ``` 15 | 16 | ## Format files 17 | 18 | `-f {filename1} {filename2} ...` or `--file {filename1} {filename2} ...` 19 | 20 | Select one or more `.TcPOU` or `.TcIO` files to format. Before any formatting is done, a `.bak` back-up copy of each file is generated. This is an extra safety measure in case unintended changes are made and/or the code is not under source control. 21 | 22 | The options `--file` and `--project` are mutually exclusive, i.e. you can only use one of them! 23 | 24 | Note: Currently _TcBlack_ only formats the declaration part of programs, function blocks, functions, methods, properties and interfaces. Formatting of the implementation is planned for a future release. 25 | 26 | ### Examples 27 | 28 | Format a single file with the short command option. 29 | 30 | ``` 31 | > TcBlack -f C:\Path\To\File1.TcPOU 32 | ``` 33 | 34 | Format multiple files with the long command option. 35 | 36 | ``` 37 | > TcBlack --file C:\Path\To\File1.TcPOU C:\Path\To\File1.TcIO 38 | ``` 39 | 40 | ## Format a plc project 41 | 42 | `-p {project}` or `--project {project}` 43 | 44 | Select a `.plcproj` file to format. _TcBlack_ tries to find all the `.TcPOU` and `.TcIO` files in the subdirectories of the `.plcproj` file. Then it will format all the found files. 45 | 46 | Before any formatting is done, a `.bak` back-up copy of each file is generated. This is an extra safety measure in case unintended changes are made and/or the code is not under source control. 47 | 48 | The options `--file` and `--project` are mutually exclusive, i.e. you can only use one of them! 49 | 50 | Note: Currently _TcBlack_ only formats the declaration part of function blocks, functions, methods, properties and interfaces. Formatting of the implementation is planned for a future release. 51 | 52 | ### Examples 53 | 54 | Format a project with the short command option. 55 | 56 | ``` 57 | > TcBlack -p C:\Path\To\PlcProject.plcproj 58 | ``` 59 | 60 | Format multiple files with the long command option. 61 | 62 | ``` 63 | > TcBlack --project C:\Path\To\PlcProject.plcproj 64 | ``` 65 | 66 | You can also use a single `.TcPOU` or `.TcIO` file from a project as an argument. It will then find all the `.TcPOU` and `.TcIO`. For example if `FB_FunctionBlock.TcPOU` is part of `PlcProject.plcproj` from the previous example, then the following command will have the same effect as the previous example. 67 | 68 | ``` 69 | > TcBlack -p C:\Path\To\Plc\FB_FunctionBlock.TcPOU 70 | ``` 71 | 72 | ## Safe mode 73 | 74 | `--safe` 75 | 76 | This will build the `.plcproj` file before and after formatting, in order to check if unintended changes were made. Unintended changes are changes in the behavior of the code. Changes are detected by comparing the generated hash of the compilation before and after formatting. The hashes are the filenames of the `.COMPILEINFO` files in the `_CompileInfo` folder of a plc project. If any changes are detected, it will revert all the files to their previous state. 77 | 78 | ### Examples 79 | 80 | Format a whole plc project in safe mode. 81 | 82 | ``` 83 | > TcBlack --safe -p C:\Path\To\PlcProject.plcproj 84 | ``` 85 | 86 | ## Indentation 87 | 88 | `--indentation {indentation}` 89 | 90 | This option overrides the standard behavior for the indentation of _TcBlack_. By default it will look if there is a tab present for each inidvidual file which it is going to format. If a tab is found, this is used as indentation. If no tab is found, four spaces are used as the indentation. This option allows you to for example equalize the indentation type across a project. 91 | 92 | ### Example 93 | 94 | Change the indentation to two spaces for a whole project. 95 | 96 | ``` 97 | > TcBlack --indentation " " -p C:\Path\To\PlcProject.plcproj 98 | ``` 99 | 100 | ## Line ending 101 | 102 | `--windowslineending` or `--unixlineending` 103 | 104 | This option overrides the standard behavior for the line ending of _TcBlack_. By default it will look if there is a Windows line ending (`\r\n`) present for each inidvidual file which it is going to format. If a `\r\n` is found, this is used as line ending. If no `\r\n` is found, the unix line ending `\n` is used. This option allows you to for example equalize the line ending type across a project. 105 | 106 | Note: it will only change the line ending of the implementation part, not for the other code in the `.TcPOU` or `.TcIO` files! 107 | 108 | ### Examples 109 | 110 | Use a windows line ending for all the files in a plc project. 111 | 112 | ``` 113 | TcBlack --windowslineending --project C:\Path\To\PlcProject.plcproj 114 | ``` 115 | 116 | Use a unix line ending for all the given files. 117 | 118 | ``` 119 | TcBlack --unixlineending --files C:\Path\To\File1.TcPOU C:\Path\To\File1.TcIO 120 | ``` 121 | 122 | ## Verbose 123 | 124 | `--verbose` 125 | 126 | Shows the commands which are used to build the project. Currently only has an effect when `--safe` option is used. 127 | 128 | ### Example 129 | 130 | ``` 131 | TcBlack --verbose --safe -f C:\Path\To\Plc\FB_FunctionBlock.TcPOU 132 | ``` 133 | ## Version 134 | 135 | `--version` 136 | 137 | Shows the version of _TcBlack_. 138 | 139 | ### Example 140 | ``` 141 | TcBlack --version 142 | ``` 143 | -------------------------------------------------------------------------------- /docs/vsix_installation.md: -------------------------------------------------------------------------------- 1 | # Installation Guide Visual Studio 2019 2 | 3 | 1. Download the VSIX file 4 | 2. Close all instances of Visual Studio 5 | 3. Double click the file and follow the installation instructions on screen 6 | 4. The Extension should now be aviaiable in the `Tools` menu bar of Visual Studio 7 | 8 | 9 | # Installation Guide Twincat XAE Shell 10 | 11 | 1. Download the VSIX file 12 | 2. Close all instances of TwinCAT XAE shell 13 | 3. Unzip the VSIX package 14 | 4. Locate your TwinCAT XAE Shell installation folder (common: `C:\\Program Files (x86)\\Beckhoff\\TcXaeShell\\`) 15 | 5. Navigate to `%InstallationFolder%/Common7\\IDE\\Extensions` 16 | 6. Create a new folder in the in the `Extensions` folder 17 | 7. Copy the contents of the unpacked VSIX package to the newly created folder 18 | 8. The Extension should now be aviaiable in the `Tools` menu bar of TcXaeShell 19 | -------------------------------------------------------------------------------- /logo/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/logo/128.png -------------------------------------------------------------------------------- /logo/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/logo/16.png -------------------------------------------------------------------------------- /logo/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/logo/256.png -------------------------------------------------------------------------------- /logo/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/logo/32.png -------------------------------------------------------------------------------- /logo/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/logo/64.png -------------------------------------------------------------------------------- /logo/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/logo/96.png -------------------------------------------------------------------------------- /logo/TcBlack_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/logo/TcBlack_icon.ico -------------------------------------------------------------------------------- /logo/TcBlack_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 34 | 36 | 39 | 42 | 45 | 48 | 51 | 54 | 55 | 57 | 59 | 65 | 66 | 69 | 71 | 81 | 82 | 85 | 95 | 96 | 99 | 109 | 110 | 113 | 123 | 124 | 126 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /logo/TcBlack_icon_square.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 41 | 43 | 46 | 49 | 52 | 55 | 58 | 61 | 62 | 65 | 68 | 78 | 79 | 82 | 84 | 94 | 95 | 98 | 108 | 109 | 112 | 122 | 123 | 126 | 136 | 137 | 139 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /logo/TcBlack_logo_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/logo/TcBlack_logo_big.png -------------------------------------------------------------------------------- /logo/TcBlack_logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/logo/TcBlack_logo_small.png -------------------------------------------------------------------------------- /src/TcBlack.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/TcBlack/FormatStructuredText.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using EnvDTE; 4 | using Microsoft.VisualStudio.Shell; 5 | using Task = System.Threading.Tasks.Task; 6 | using TcBlackCore; 7 | using TCatSysManagerLib; 8 | 9 | namespace TcBlack 10 | { 11 | /// 12 | /// Command handler 13 | /// 14 | internal sealed class FormatStructuredText 15 | { 16 | /// 17 | /// Command ID. 18 | /// 19 | public const int CommandId = 0x0100; 20 | 21 | /// 22 | /// Command menu group (command set GUID). 23 | /// 24 | public static readonly Guid CommandSet = new Guid("2331eac3-39e5-4347-b678-a146d49c0a07"); 25 | 26 | /// 27 | /// VS Package that provides this command, not null. 28 | /// 29 | private readonly AsyncPackage package; 30 | 31 | /// 32 | /// Initializes a new instance of the class. 33 | /// Adds our command handlers for menu (commands must exist in the command table file) 34 | /// 35 | /// Owner package, not null. 36 | /// Command service to add command to, not null. 37 | private FormatStructuredText(AsyncPackage package, OleMenuCommandService commandService) 38 | { 39 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 40 | commandService = 41 | commandService ?? throw new ArgumentNullException(nameof(commandService)); 42 | 43 | var menuCommandID = new CommandID(CommandSet, CommandId); 44 | var menuItem = new MenuCommand(Execute, menuCommandID); 45 | commandService.AddCommand(menuItem); 46 | } 47 | 48 | /// 49 | /// Gets the instance of the command. 50 | /// 51 | public static FormatStructuredText Instance { get; private set; } 52 | 53 | /// 54 | /// Gets the service provider from the owner package. 55 | /// 56 | private IAsyncServiceProvider ServiceProvider 57 | { 58 | get { return this.package; } 59 | } 60 | 61 | /// 62 | /// Initializes the singleton instance of the command. 63 | /// 64 | /// Owner package, not null. 65 | public static async Task InitializeAsync(AsyncPackage package) 66 | { 67 | // Switch to the main thread - the call to AddCommand in FormatStructuredText's constructor requires 68 | // the UI thread. 69 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 70 | 71 | OleMenuCommandService commandService = 72 | await package.GetServiceAsync((typeof(IMenuCommandService))) 73 | as OleMenuCommandService; 74 | Instance = new FormatStructuredText(package, commandService); 75 | } 76 | 77 | /// 78 | /// This function is the callback used to execute the command when the menu item is clicked. 79 | /// See the constructor to see how the menu item is associated with this function using 80 | /// OleMenuCommandService service and MenuCommand class. 81 | /// 82 | /// Event sender. 83 | /// Event args. 84 | private void Execute(object sender, EventArgs e) 85 | { 86 | ThreadHelper.ThrowIfNotOnUIThread(); 87 | 88 | DTE dte = Package.GetGlobalService(typeof(DTE)) as DTE; 89 | if (dte.ActiveWindow.ProjectItem.Object is ITcPlcDeclaration declaration) 90 | { 91 | dte.ActiveDocument.Save(""); 92 | 93 | int indents = 0; 94 | string text = declaration.DeclarationText; 95 | TcBlackCore.Globals.indentation = text.Contains("\t") ? "\t" : " "; 96 | TcBlackCore.Globals.lineEnding = "\r\n"; 97 | string formatedCode = new CompositeCode(text).Format(ref indents); 98 | declaration.DeclarationText = formatedCode; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/TcBlack/FormatStructuredTextPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | using Microsoft.VisualStudio.Shell; 6 | using Task = System.Threading.Tasks.Task; 7 | 8 | [assembly: CLSCompliant(false)] 9 | 10 | namespace TcBlack 11 | { 12 | /// 13 | /// This is the class that implements the package exposed by this assembly. 14 | /// 15 | /// 16 | /// 17 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 18 | /// is to implement the IVsPackage interface and register itself with the shell. 19 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 20 | /// to do it: it derives from the Package class that provides the implementation of the 21 | /// IVsPackage interface and uses the registration attributes defined in the framework to 22 | /// register itself and its components with the shell. These attributes tell the pkgdef creation 23 | /// utility what data to put into .pkgdef file. 24 | /// 25 | /// 26 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. 27 | /// 28 | /// 29 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 30 | [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About 31 | [ProvideMenuResource("Menus.ctmenu", 1)] 32 | [Guid(FormatStructuredTextPackage.PackageGuidString)] 33 | [SuppressMessage( 34 | "StyleCop.CSharp.DocumentationRules", 35 | "SA1650:ElementDocumentationMustBeSpelledCorrectly", 36 | Justification = "pkgdef, VS and vsixmanifest are valid VS terms" 37 | )] 38 | public sealed class FormatStructuredTextPackage : AsyncPackage 39 | { 40 | /// 41 | /// FormatStructuredTextPackage GUID string. 42 | /// 43 | public const string PackageGuidString = "f0d2f155-5908-42b2-a15d-9e628c6f43b4"; 44 | 45 | /// 46 | /// Initializes a new instance of the class. 47 | /// 48 | public FormatStructuredTextPackage() 49 | { 50 | // Inside this method you can place any initialization code that does not require 51 | // any Visual Studio service because at this point the package object is created but 52 | // not sited yet inside Visual Studio environment. The place to do all the other 53 | // initialization is the Initialize method. 54 | } 55 | 56 | #region Package Members 57 | 58 | /// 59 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 60 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 61 | /// 62 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. 63 | /// A provider for progress updates. 64 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. 65 | protected override async Task InitializeAsync( 66 | CancellationToken cancellationToken, 67 | IProgress progress 68 | ) 69 | { 70 | // When initialized asynchronously, the current thread may be a background thread at this point. 71 | // Do any initialization that requires the UI thread after switching to the UI thread. 72 | await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 73 | await FormatStructuredText.InitializeAsync(this); 74 | } 75 | 76 | #endregion 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/TcBlack/FormatStructuredTextPackage.vsct: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 48 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/TcBlack/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Roald Ruiter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/TcBlack/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TcBlack")] 9 | [assembly: AssemblyDescription("Opinionated formatter for TwinCAT Structured Text")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Roald Ruiter")] 12 | [assembly: AssemblyProduct("TcBlack")] 13 | [assembly: AssemblyCopyright("Copyright 2022")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("0.1.1.0")] 33 | -------------------------------------------------------------------------------- /src/TcBlack/References/TCatSysManagerLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/src/TcBlack/References/TCatSysManagerLib.dll -------------------------------------------------------------------------------- /src/TcBlack/TcBlack_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/src/TcBlack/TcBlack_icon.ico -------------------------------------------------------------------------------- /src/TcBlack/VSPackage.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | FormatStructuredText Extension 122 | 123 | 124 | FormatStructuredText Visual Studio Extension Detailed Info 125 | 126 | -------------------------------------------------------------------------------- /src/TcBlack/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/TcBlack/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | TcBlack 6 | Opinionated formatter for TwinCAT Structured Text. 7 | https://github.com/Roald87/TcBlack/ 8 | LICENSE.txt 9 | https://github.com/Roald87/TcBlack/ 10 | https://github.com/Roald87/TcBlack/releases 11 | TcBlack_icon.ico 12 | twincat structured text opninionated code formatter 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/TcBlack/stylesheet.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | border: 0; 5 | color: #1e1e1e; 6 | font-size: 13px; 7 | font-family: "Segoe UI", Helvetica, Arial, sans-serif; 8 | line-height: 1.45; 9 | word-wrap: break-word; 10 | } 11 | 12 | /* General & 'Reset' Stuff */ 13 | 14 | .container { 15 | width: 980px; 16 | margin: 0 auto; 17 | } 18 | 19 | section { 20 | display: block; 21 | margin: 0; 22 | } 23 | 24 | h1, 25 | h2, 26 | h3, 27 | h4, 28 | h5, 29 | h6 { 30 | margin: 0; 31 | } 32 | 33 | /* Header,
34 | header - container 35 | h1 - project name 36 | h2 - project description 37 | */ 38 | 39 | #header { 40 | color: #fff; 41 | background: #68217a; 42 | position: relative; 43 | } 44 | #hangcloud { 45 | width: 190px; 46 | height: 160px; 47 | background: url("../images/bannerart03.png"); 48 | position: absolute; 49 | top: 0; 50 | right: -30px; 51 | } 52 | h1, 53 | h2 { 54 | font-family: "Segoe UI Light", "Segoe UI", Helvetica, Arial, sans-serif; 55 | line-height: 1; 56 | margin: 0 18px; 57 | padding: 0; 58 | } 59 | #header h1 { 60 | font-size: 3.4em; 61 | padding-top: 18px; 62 | font-weight: normal; 63 | margin-left: 15px; 64 | } 65 | 66 | #header h2 { 67 | font-size: 1.5em; 68 | margin-top: 10px; 69 | padding-bottom: 18px; 70 | font-weight: normal; 71 | } 72 | 73 | #main_content { 74 | width: 100%; 75 | display: flex; 76 | flex-direction: row; 77 | } 78 | 79 | h1, 80 | h2, 81 | h3, 82 | h4, 83 | h5, 84 | h6 { 85 | font-weight: bolder; 86 | } 87 | 88 | #main_content h1 { 89 | font-size: 1.8em; 90 | margin-top: 34px; 91 | } 92 | 93 | #main_content h1:first-child { 94 | margin-top: 30px; 95 | } 96 | 97 | #main_content h2 { 98 | font-size: 1.4em; 99 | font-weight: bold; 100 | } 101 | p, 102 | ul { 103 | margin: 11px 18px; 104 | } 105 | 106 | #main_content a { 107 | color: #06c; 108 | text-decoration: none; 109 | } 110 | ul { 111 | margin-top: 13px; 112 | margin-left: 18px; 113 | padding-left: 0; 114 | } 115 | ul li { 116 | margin-left: 18px; 117 | padding-left: 0; 118 | } 119 | #lpanel { 120 | width: 620px; 121 | float: left; 122 | } 123 | #rpanel ul { 124 | list-style-type: none; 125 | width: 300px; 126 | } 127 | #rpanel ul li { 128 | line-height: 1.8em; 129 | } 130 | #rpanel { 131 | background: #e7e7e7; 132 | width: 360px; 133 | float: right; 134 | } 135 | 136 | #rpanel div { 137 | width: 300px; 138 | } 139 | -------------------------------------------------------------------------------- /src/TcBlackCLI/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/TcBlackCLI/Backup.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace TcBlackCLI 4 | { 5 | public class Backup 6 | { 7 | private string _filename; 8 | private string backUpFilename; 9 | 10 | /// 11 | /// Creates a backup .bak file of the original file. 12 | /// 13 | /// Filename to back-up. 14 | public Backup(string filename) 15 | { 16 | _filename = filename; 17 | backUpFilename = $"{filename}.bak"; 18 | File.Copy(_filename, backUpFilename, true); 19 | } 20 | 21 | /// 22 | /// Overwrite the original file with the .bak file. 23 | /// 24 | public Backup Restore() 25 | { 26 | File.Copy(backUpFilename, _filename, true); 27 | 28 | return this; 29 | } 30 | 31 | /// 32 | /// Deletes the .bak file. 33 | /// 34 | public void Delete() 35 | { 36 | File.Delete(backUpFilename); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/TcBlackCLI/BuildTwinCatProject.bat: -------------------------------------------------------------------------------- 1 | start /wait "" %1 %2 /Project %3 /Build "Debug|TwinCAT RT (x64)" /Out build.log -------------------------------------------------------------------------------- /src/TcBlackCLI/Options.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CommandLine; 3 | 4 | namespace TcBlackCore 5 | { 6 | /// 7 | /// Options for command line interface. 8 | /// 9 | public class Options 10 | { 11 | [Option('f', "file", HelpText = "TcPOU/TcIO file(s) to format.", SetName = "files")] 12 | public IEnumerable File { get; set; } 13 | 14 | [Option( 15 | 'p', 16 | "project", 17 | Default = "", 18 | HelpText = "Plc project to format.", 19 | SetName = "files" 20 | )] 21 | public string Project { get; set; } 22 | 23 | [Option( 24 | Default = false, 25 | HelpText = "Compiles project before and after formatting, in order to check " 26 | + "if the code has changed. WARNING: Takes > 30 seconds!" 27 | )] 28 | public bool Safe { get; set; } 29 | 30 | [Option( 31 | Default = false, 32 | HelpText = "Shows the commands which are used to build the project. " 33 | + "Currently only has an effect when --safe option is used." 34 | )] 35 | public bool Verbose { get; set; } 36 | 37 | [Option(Default = "", HelpText = "Override the indentation found in the file(s).")] 38 | public string Indentation { get; set; } 39 | 40 | [Option(HelpText = "Overrides the line ending of all files with Windows' \\r\\n")] 41 | public bool WindowsLineEnding { get; set; } 42 | 43 | [Option(HelpText = "Overrides the line ending of all files with UNIX' \\n.")] 44 | public bool UnixLineEnding { get; set; } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/TcBlackCLI/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Text.RegularExpressions; 5 | using System.Linq; 6 | using CommandLine; 7 | using TcBlackCore; 8 | 9 | [assembly: CLSCompliant(true)] 10 | 11 | namespace TcBlackCLI 12 | { 13 | class Program 14 | { 15 | [STAThread] 16 | static void Main(string[] args) 17 | { 18 | Parser.Default 19 | .ParseArguments(args) 20 | .WithParsed(options => 21 | { 22 | string[] filenames; 23 | try 24 | { 25 | filenames = FilesToFormat(options); 26 | } 27 | catch (ArgumentException) 28 | { 29 | Console.WriteLine($"Unable to find file {options.Project}"); 30 | return; 31 | } 32 | 33 | string fileListForCommandPrompt = string.Join( 34 | "\n", 35 | filenames.Select(filename => $" - {filename}").ToArray() 36 | ); 37 | 38 | if (options.Safe) 39 | { 40 | Console.WriteLine( 41 | $"\nFormatting {filenames.Length} file(s) " 42 | + $"in safe mode:\n{fileListForCommandPrompt}\n" 43 | ); 44 | SafeFormat(filenames, options); 45 | } 46 | else 47 | { 48 | Console.WriteLine( 49 | $"\nFormatting {filenames.Length} file(s) " 50 | + $"in fast non-safe mode:\n{fileListForCommandPrompt}\n" 51 | ); 52 | try 53 | { 54 | CreateBackups(options.File.ToArray()); 55 | } 56 | catch (FileNotFoundException) 57 | { 58 | Console.WriteLine( 59 | $"One of the files doesn't exist. " 60 | + $"Check the filenames and try again." 61 | ); 62 | return; 63 | } 64 | FormatAll(filenames, options); 65 | } 66 | }); 67 | } 68 | 69 | /// 70 | /// Return all the files which should be formatted. 71 | /// 72 | /// Input from the user. 73 | /// Array with full path to the files. 74 | static string[] FilesToFormat(Options options) 75 | { 76 | if (options.Project.Length > 0) 77 | { 78 | Regex extensions = new Regex(@"(TcPOU|TcIO)$"); 79 | string projectDirectory = Path.GetDirectoryName(options.Project); 80 | return Directory 81 | .EnumerateFiles(projectDirectory, "*.*", SearchOption.AllDirectories) 82 | .Where(f => extensions.IsMatch(f)) 83 | .ToArray(); 84 | } 85 | else 86 | { 87 | return options.File.ToArray(); 88 | } 89 | } 90 | 91 | /// 92 | /// Compiles project before and after formatting to check if nothing changed. 93 | /// It does this by comparing the compile hash. 94 | /// 95 | /// Files to format. 96 | /// Input from the command line. 97 | static void SafeFormat(string[] filenames, Options options) 98 | { 99 | Console.WriteLine("Building project before formatting."); 100 | TcProjectBuilder tcProject; 101 | try 102 | { 103 | tcProject = new TcProjectBuilder(filenames.First()); 104 | } 105 | catch (FileNotFoundException ex) 106 | { 107 | Console.WriteLine($"{ex.Message}\nCancelling build."); 108 | return; 109 | } 110 | 111 | string hashBeforeFormat = string.Empty; 112 | try 113 | { 114 | hashBeforeFormat = tcProject.Build(options.Verbose).Hash; 115 | } 116 | catch (ProjectBuildFailedException) 117 | { 118 | Console.WriteLine("Initial project build failed! No formatting will be done."); 119 | return; 120 | } 121 | 122 | List backups; 123 | try 124 | { 125 | backups = CreateBackups(filenames); 126 | } 127 | catch (FileNotFoundException) 128 | { 129 | Console.WriteLine( 130 | $"One of the files doesn't exist. " + $"Check the filenames and try again." 131 | ); 132 | return; 133 | } 134 | FormatAll(filenames, options); 135 | 136 | Console.WriteLine("Building project after formatting."); 137 | string hashAfterFormat = string.Empty; 138 | try 139 | { 140 | hashAfterFormat = tcProject.Build(options.Verbose).Hash; 141 | } 142 | catch (ProjectBuildFailedException) 143 | { 144 | Console.WriteLine("Project build failed after formatting! Undoing it."); 145 | backups.ForEach(backup => backup.Restore().Delete()); 146 | return; 147 | } 148 | 149 | if (hashBeforeFormat != hashAfterFormat) 150 | { 151 | Console.WriteLine("Something when wrong during formatting! Undoing it."); 152 | backups.ForEach(backup => backup.Restore().Delete()); 153 | } 154 | else 155 | { 156 | Console.WriteLine("All done and everything looks OK!"); 157 | } 158 | } 159 | 160 | /// 161 | /// Reformat all TcPou and TcIO files. 162 | /// 163 | /// Files to format. 164 | /// Options to use for the formatting. 165 | static void FormatAll(string[] filenames, Options options) 166 | { 167 | foreach (string filename in filenames) 168 | { 169 | if ( 170 | options.Indentation.Length > 0 171 | && (options.WindowsLineEnding || options.UnixLineEnding) 172 | ) 173 | { 174 | new TcPou(filename, options.Indentation, options.WindowsLineEnding) 175 | .Format() 176 | .Save(); 177 | } 178 | else if (options.Indentation.Length > 0) 179 | { 180 | new TcPou(filename, options.Indentation).Format().Save(); 181 | } 182 | else if (options.WindowsLineEnding || options.UnixLineEnding) 183 | { 184 | new TcPou(filename, options.WindowsLineEnding).Format().Save(); 185 | } 186 | else 187 | { 188 | new TcPou(filename).Format().Save(); 189 | } 190 | } 191 | Console.WriteLine($"Formatted {filenames.Count()} file(s)."); 192 | } 193 | 194 | /// 195 | /// Creates .bak files of the files. 196 | /// 197 | /// File to back-up. 198 | /// List of files which were backed-up and can be restored. 199 | static List CreateBackups(string[] filenames) 200 | { 201 | return filenames.Select(filename => new Backup(filename)).ToList(); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/TcBlackCLI/ProjectBuildFailedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace TcBlackCLI 5 | { 6 | [Serializable] 7 | public class ProjectBuildFailedException : Exception 8 | { 9 | public ProjectBuildFailedException() { } 10 | 11 | public ProjectBuildFailedException(string message) : base(message) { } 12 | 13 | public ProjectBuildFailedException(string message, Exception innerException) 14 | : base(message, innerException) { } 15 | 16 | protected ProjectBuildFailedException(SerializationInfo info, StreamingContext context) 17 | : base(info, context) { } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/TcBlackCLI/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TcBlackCLI")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("TcBlackCLI")] 12 | [assembly: AssemblyCopyright("Copyright © 2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("6e641f94-d4be-4576-a2a1-ae02bc173716")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("0.3.0.0")] 35 | -------------------------------------------------------------------------------- /src/TcBlackCLI/TcBlackCLI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6E641F94-D4BE-4576-A2A1-AE02BC173716} 8 | Exe 9 | TcBlackCLI 10 | TcBlackCLI 11 | v4.8 12 | 512 13 | true 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | ..\TcBlack.ruleset 26 | true 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | ..\TcBlack.ruleset 37 | true 38 | 39 | 40 | false 41 | 42 | 43 | TcBlackCLI.pfx 44 | 45 | 46 | TcBlack_icon.ico 47 | 48 | 49 | 50 | ..\packages\CommandLineParser.2.8.0\lib\net461\CommandLine.dll 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | PreserveNewest 74 | 75 | 76 | Designer 77 | 78 | 79 | 80 | 81 | 82 | {00020430-0000-0000-C000-000000000046} 83 | 2 84 | 0 85 | 0 86 | primary 87 | False 88 | True 89 | 90 | 91 | 92 | 93 | {434c7a68-c27f-4f46-977e-2f60441feb64} 94 | TcBlackCore 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/TcBlackCLI/TcBlack_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/src/TcBlackCLI/TcBlack_icon.ico -------------------------------------------------------------------------------- /src/TcBlackCLI/TcPou.cs: -------------------------------------------------------------------------------- 1 | using System.Xml; 2 | using TcBlackCore; 3 | 4 | namespace TcBlackCLI 5 | { 6 | /// 7 | /// Load, format and save a TcPOU file. 8 | /// 9 | public class TcPou 10 | { 11 | private XmlDocument doc; 12 | private string tcPouPath; 13 | private string text; 14 | 15 | /// 16 | /// Loads a TcPOU file. 17 | /// 18 | /// Path to the TcPOU file. 19 | public TcPou(string path) 20 | { 21 | tcPouPath = path; 22 | doc = new XmlDocument(); 23 | doc.Load(path); 24 | 25 | text = doc.InnerXml; 26 | Globals.lineEnding = text.Contains("\r\n") ? "\r\n" : "\n"; 27 | Globals.indentation = text.Contains("\t") ? "\t" : " "; 28 | } 29 | 30 | /// 31 | /// Loads a TcPOU file. 32 | /// 33 | /// Path to the TcPOU file. 34 | /// 35 | /// Which indentation to use in the formatted file. 36 | /// 37 | public TcPou(string path, string indentation) : this(path) 38 | { 39 | Globals.indentation = indentation; 40 | } 41 | 42 | /// 43 | /// Loads a TcPOU file. 44 | /// 45 | /// Path to the TcPOU file. 46 | /// If true use '\r\n' else uses '\n'. 47 | public TcPou(string path, bool windowsLineEnding) : this(path) 48 | { 49 | Globals.lineEnding = windowsLineEnding ? "\r\n" : "\n"; 50 | } 51 | 52 | /// 53 | /// Loads a TcPOU file. 54 | /// 55 | /// Path to the TcPOU file. 56 | /// 57 | /// Which indentation to use in the formatted file. 58 | /// 59 | /// If true use '\r\n' else uses '\n'. 60 | public TcPou(string path, string indentation, bool windowsLineEnding) 61 | : this(path, windowsLineEnding) 62 | { 63 | Globals.indentation = indentation; 64 | } 65 | 66 | /// 67 | /// Format the TwinCAT TcPOU file. 68 | /// 69 | /// The formatted TcPOU object. 70 | public TcPou Format() 71 | { 72 | int indents = 0; 73 | XmlNodeList nodes = doc.SelectNodes(".//Declaration"); 74 | foreach (XmlNode node in nodes) 75 | { 76 | string formattedCode = new CompositeCode(node.InnerText).Format(ref indents); 77 | node.InnerXml = $""; 78 | } 79 | 80 | return this; 81 | } 82 | 83 | /// 84 | /// Overwrites the currently saved TcPOU file on disk. 85 | /// 86 | public void Save() 87 | { 88 | using ( 89 | var w = XmlWriter.Create( 90 | tcPouPath, 91 | new XmlWriterSettings { Indent = true, NewLineChars = Globals.lineEnding, } 92 | ) 93 | ) 94 | { 95 | doc.Save(w); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/TcBlackCLI/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/BackupTestData/BackupFileAlreadyExists.txt: -------------------------------------------------------------------------------- 1 | Some text. -------------------------------------------------------------------------------- /src/TcBlackCLITests/BackupTestData/InitializeObjectCreatesBackupFile.txt: -------------------------------------------------------------------------------- 1 | Some text. -------------------------------------------------------------------------------- /src/TcBlackCLITests/BackupTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using TcBlackCLI; 4 | using Xunit; 5 | 6 | [assembly: CLSCompliant(false)] 7 | 8 | namespace TcBlackTests 9 | { 10 | public class BackupTests 11 | { 12 | readonly string testDataPath = Path.GetFullPath("../../BackupTestData/"); 13 | 14 | [Fact] 15 | public void InitializeObjectCreatesBackupFile() 16 | { 17 | string filename = Path.Combine(testDataPath, "InitializeObjectCreatesBackupFile.txt"); 18 | Backup backup = new Backup(filename); 19 | // Shouldn't raise a DirectoryNotFoundException 20 | File.ReadAllText(filename + ".bak"); 21 | // Clean up 22 | backup.Delete(); 23 | } 24 | 25 | [Fact] 26 | [System.Diagnostics.CodeAnalysis.SuppressMessage( 27 | "Microsoft.Usage", 28 | "CA1806: Do not ignore method results.", 29 | Justification = "Only check that no execption is raised if the file already exists." 30 | )] 31 | public void BackupFileAlreadyExistsShouldOverwriteBackupFile() 32 | { 33 | string filename = Path.Combine(testDataPath, "BackupFileAlreadyExists.txt"); 34 | // Shouldn't raise an exception that the file already exists 35 | new Backup(filename); 36 | } 37 | 38 | [Fact] 39 | public void RestoreBackupFile() 40 | { 41 | string filename = Path.Combine(testDataPath, "RestoreFileTest.txt"); 42 | string originalText = "Original text."; 43 | File.AppendAllText(filename, originalText); 44 | Assert.Equal(originalText, File.ReadAllText(filename)); 45 | 46 | Backup backup = new Backup(filename); 47 | string newText = "New text."; 48 | File.WriteAllText(filename, newText); 49 | Assert.Equal(newText, File.ReadAllText(filename)); 50 | 51 | backup.Restore(); 52 | Assert.Equal(originalText, File.ReadAllText(filename)); 53 | 54 | File.Delete(filename); 55 | File.Delete($"{filename}.bak"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/MockTcProjectBuilder.cs: -------------------------------------------------------------------------------- 1 | using TcBlackCLI; 2 | 3 | namespace TcBlackTests 4 | { 5 | public class MockTcProjectBuilder : TcProjectBuilder 6 | { 7 | public MockTcProjectBuilder(string projectPath, string buildLogPath) : base(projectPath) 8 | { 9 | BuildLogFile = buildLogPath; 10 | } 11 | 12 | /// 13 | /// Doesn't run cmd.exe in the mock implementation. 14 | /// 15 | /// 16 | /// This argument doesn't have an effect in the mock implementation 17 | /// 18 | protected override void ExecuteCommand(string command, bool verbose) { } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TcBlackCLITests")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("TcBlackCLITests")] 12 | [assembly: AssemblyCopyright("Copyright © 2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("87dfda93-482c-4c04-abf1-50e23f24f85d")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("0.3.0.0")] 35 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcBlackCLITests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Debug 9 | AnyCPU 10 | {87DFDA93-482C-4C04-ABF1-50E23F24F85D} 11 | Library 12 | Properties 13 | TcBlackCLITests 14 | TcBlackCLITests 15 | v4.8 16 | 512 17 | true 18 | 19 | 20 | 21 | 22 | 23 | true 24 | full 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | prompt 29 | 4 30 | ..\TcBlackTests.ruleset 31 | true 32 | 33 | 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | ..\TcBlackTests.ruleset 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll 54 | 55 | 56 | ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll 57 | 58 | 59 | ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll 60 | 61 | 62 | ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Designer 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | {6e641f94-d4be-4576-a2a1-ae02bc173716} 103 | TcBlackCLI 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_ExpectedComplex.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_ExpectedOverrideIndentation.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_ExpectedOverrideLineEnding.TcPOU: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_ExpectedOverrideLineEndingAndIndentation.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_ExpectedSimple.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_ExpectedTabAndUnixLineEnd.TcPOU: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_ExpectedWithEmptyVars.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_ExpectedWithPropertiesAndMethods.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 13 | 14 | 2 THEN 20 | Counter := Counter + 5 ; 21 | END_IF 22 | END_IF 23 | 24 | Base(Variable1:=2, Variable2:=3 , Variable3:= 5,Sentence:='', Conditions :=Conditions); 25 | 26 | 27 | AddTwoInts( Variable1 :=4, 28 | Variable2:=4);]]> 29 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 71 | 72 | 73 | 74 | 75 | 76 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_InputComplex.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_InputOverrideIndentation.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_InputOverrideLineEnding.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_InputOverrideLineEndingAndIndentation.TcPOU: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_InputSimple.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_InputTabAndUnixLineEnd.TcPOU: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_InputWithEmptyVars.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTestData/FB_InputWithPropertiesAndMethods.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 13 | 14 | 2 THEN 20 | Counter := Counter + 5 ; 21 | END_IF 22 | END_IF 23 | 24 | Base(Variable1:=2, Variable2:=3 , Variable3:= 5,Sentence:='', Conditions :=Conditions); 25 | 26 | 27 | AddTwoInts( Variable1 :=4, 28 | Variable2:=4);]]> 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 50 | 51 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 68 | 69 | 70 | 71 | 72 | 73 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcPouTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using TcBlackCLI; 4 | using Xunit; 5 | 6 | namespace TcBlackTests 7 | { 8 | [Collection("Sequential")] 9 | public class TcPouTests 10 | { 11 | private static string workingDirectory = Environment.CurrentDirectory; 12 | private static string testDataDirectory = Path.Combine( 13 | Directory.GetParent(workingDirectory).Parent.FullName, 14 | "TcPouTestData" 15 | ); 16 | 17 | [Theory] 18 | [InlineData("FB_InputSimple.TcPOU", "FB_ExpectedSimple.TcPOU")] 19 | [InlineData("FB_InputComplex.TcPOU", "FB_ExpectedComplex.TcPOU")] 20 | [InlineData( 21 | "FB_InputWithPropertiesAndMethods.TcPOU", 22 | "FB_ExpectedWithPropertiesAndMethods.TcPOU" 23 | )] 24 | [InlineData("FB_InputWithEmptyVars.TcPOU", "FB_ExpectedWithEmptyVars.TcPOU")] 25 | public void LoadChangeAndSaveDeclaration(string fbInput, string fbExpected) 26 | { 27 | string fileToFormat = Path.Combine(testDataDirectory, fbInput); 28 | Backup backup = new Backup(fileToFormat); 29 | 30 | new TcPou(fileToFormat).Format().Save(); 31 | 32 | string expectedFile = Path.Combine(testDataDirectory, fbExpected); 33 | string expected = File.ReadAllText(expectedFile); 34 | string actual = File.ReadAllText(fileToFormat); 35 | backup.Restore().Delete(); 36 | Assert.Equal(expected, actual); 37 | } 38 | 39 | [Fact] 40 | public void FormatFileWithUnixTypeLineEnd() 41 | { 42 | string fileToFormat = Path.Combine( 43 | testDataDirectory, 44 | "FB_InputTabAndUnixLineEnd.TcPOU" 45 | ); 46 | // git keeps changing the line endings. In order to make sure it uses the 47 | // correct line ending, I'll change them manually here. 48 | ReplaceWindowsLineEndingForUnixOnes(fileToFormat); 49 | Backup backup = new Backup(fileToFormat); 50 | 51 | new TcPou(fileToFormat).Format().Save(); 52 | 53 | string expectedFile = Path.Combine( 54 | testDataDirectory, 55 | "FB_ExpectedTabAndUnixLineEnd.TcPOU" 56 | ); 57 | ReplaceWindowsLineEndingForUnixOnes(expectedFile); 58 | string expected = File.ReadAllText(expectedFile); 59 | string actual = File.ReadAllText(fileToFormat); 60 | backup.Restore().Delete(); 61 | Assert.DoesNotContain("\r\n", expected, StringComparison.Ordinal); 62 | Assert.DoesNotContain("\r\n", actual, StringComparison.Ordinal); 63 | Assert.Equal(expected, actual); 64 | } 65 | 66 | [Fact] 67 | private void OverrideIndentationOfFile() 68 | { 69 | string fileToFormat = Path.Combine( 70 | testDataDirectory, 71 | "FB_InputOverrideIndentation.TcPOU" 72 | ); 73 | Backup backup = new Backup(fileToFormat); 74 | 75 | new TcPou(fileToFormat, indentation: " ").Format().Save(); 76 | 77 | string expectedFile = Path.Combine( 78 | testDataDirectory, 79 | "FB_ExpectedOverrideIndentation.TcPOU" 80 | ); 81 | string expected = File.ReadAllText(expectedFile); 82 | string actual = File.ReadAllText(fileToFormat); 83 | backup.Restore().Delete(); 84 | Assert.Equal(expected, actual); 85 | } 86 | 87 | [Fact] 88 | private void OverrideLineBreakOfFile() 89 | { 90 | string fileToFormat = Path.Combine( 91 | testDataDirectory, 92 | "FB_InputOverrideLineEnding.TcPOU" 93 | ); 94 | Backup backup = new Backup(fileToFormat); 95 | 96 | new TcPou(fileToFormat, windowsLineEnding: false).Format().Save(); 97 | 98 | string expectedFile = Path.Combine( 99 | testDataDirectory, 100 | "FB_ExpectedOverrideLineEnding.TcPOU" 101 | ); 102 | ReplaceWindowsLineEndingForUnixOnes(expectedFile); 103 | string expected = File.ReadAllText(expectedFile); 104 | string actual = File.ReadAllText(fileToFormat); 105 | backup.Restore().Delete(); 106 | Assert.Equal(expected, actual); 107 | } 108 | 109 | [Fact] 110 | private void OverrideLineBreakAndIndentationOfFile() 111 | { 112 | string fileToFormat = Path.Combine( 113 | testDataDirectory, 114 | "FB_InputOverrideLineEndingAndIndentation.TcPOU" 115 | ); 116 | ReplaceWindowsLineEndingForUnixOnes(fileToFormat); 117 | Backup backup = new Backup(fileToFormat); 118 | 119 | new TcPou(fileToFormat, windowsLineEnding: true, indentation: " ").Format().Save(); 120 | 121 | string expectedFile = Path.Combine( 122 | testDataDirectory, 123 | "FB_ExpectedOverrideLineEndingAndIndentation.TcPOU" 124 | ); 125 | string expected = File.ReadAllText(expectedFile); 126 | string actual = File.ReadAllText(fileToFormat); 127 | backup.Restore().Delete(); 128 | Assert.Equal(expected, actual); 129 | } 130 | 131 | private void ReplaceWindowsLineEndingForUnixOnes(string filename) 132 | { 133 | string fileContent = File.ReadAllText(filename).Replace("\r\n", "\n"); 134 | File.WriteAllText(filename, fileContent); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcProjectBuildTestData/failedBuildWithExtraTextBelow.log: -------------------------------------------------------------------------------- 1 | ------ Build started: Project: PLC (BrokenProject\PLC), Configuration: Debug TwinCAT RT (x64) ------ 2 | ------ Build started: Application: BrokenProject.PLC ------- 3 | typify code ... 4 | C:\Users\roald\Source\Repos\TcBlack\src\BrokenProject\PLC\POUs\MAIN.TcPOU(5) : error: Type definition expected instead of '' 5 | C:\Users\roald\Source\Repos\TcBlack\src\BrokenProject\PLC\POUs\MAIN.TcPOU(2) : error: 'END_VAR' expected instead of '' 6 | C:\Users\roald\Source\Repos\TcBlack\src\BrokenProject\PLC\POUs\MAIN.TcPOU(3) : error: ',, AT or :' expected instead of 'END_VAR' 7 | Compile complete -- 3 errors, 0 warnings 8 | Build complete -- 3 errors, 0 warnings : no download possible! 9 | WRN | 7-6-2020 10:57:49 834 ms | 'TwinCAT XAE': Project 'PLC' build for platform 'TwinCAT RT (x64)' failed. 10 | ========== Build: 0 succeeded or up-to-date, 1 failed, 0 skipped ========== 11 | NuGet package restore canceled. 12 | 13 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcProjectBuildTestData/firstBuildOkSecondBuildFailed.log: -------------------------------------------------------------------------------- 1 | ------ Build started: Project: PLC, Configuration: Debug TwinCAT RT (x64) ------ 2 | ------ Build started: Application: TwinCAT_Project1.PLC ------- 3 | typify code ... 4 | generate code... 5 | generate global initializations ... 6 | generate code initialization ... 7 | generate relocations ... 8 | Size of generated code: 49528 bytes 9 | Size of global data: 11761 bytes 10 | Total allocated memory size for code and data: 445720 bytes 11 | Memory area 0 contains Data, Input, Output, Memory, Code, Persistent Data and Nonsafe Data: size: 1048576 bytes, highest used address: 445720, largest contiguous memory gap: 602856 bytes (57 %) 12 | Build complete -- 0 errors, 0 warnings : ready for download! 13 | Generate TMC information ... 14 | Import symbol information ... 15 | PLC.PLC : message: generate boot information... 16 | ------ Build started: Project: TwinCAT Project1, Configuration: Debug TwinCAT RT (x64) ------ 17 | ========== Build: 2 succeeded or up-to-date, 0 failed, 0 skipped ========== 18 | 19 | ------ Build started: Project: PLC (BrokenProject\PLC), Configuration: Debug TwinCAT RT (x64) ------ 20 | ------ Build started: Application: BrokenProject.PLC ------- 21 | typify code ... 22 | C:\Users\roald\Source\Repos\TcBlack\src\BrokenProject\PLC\POUs\MAIN.TcPOU(5) : error: Type definition expected instead of '' 23 | C:\Users\roald\Source\Repos\TcBlack\src\BrokenProject\PLC\POUs\MAIN.TcPOU(2) : error: 'END_VAR' expected instead of '' 24 | C:\Users\roald\Source\Repos\TcBlack\src\BrokenProject\PLC\POUs\MAIN.TcPOU(3) : error: ',, AT or :' expected instead of 'END_VAR' 25 | Compile complete -- 3 errors, 0 warnings 26 | Build complete -- 3 errors, 0 warnings : no download possible! 27 | WRN | 7-6-2020 10:57:49 834 ms | 'TwinCAT XAE': Project 'PLC' build for platform 'TwinCAT RT (x64)' failed. 28 | ========== Build: 0 succeeded or up-to-date, 1 failed, 0 skipped ========== -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcProjectBuildTestData/succesfulBuild.log: -------------------------------------------------------------------------------- 1 | 2 | The operation could not be completed. De parameter is onjuist. 3 | 4 | ------ Build started: Project: PLC, Configuration: Debug TwinCAT RT (x64) ------ 5 | ------ Build started: Application: TwinCAT_Project1.PLC ------- 6 | typify code ... 7 | generate code... 8 | generate global initializations ... 9 | generate code initialization ... 10 | generate relocations ... 11 | Size of generated code: 49528 bytes 12 | Size of global data: 11761 bytes 13 | Total allocated memory size for code and data: 445720 bytes 14 | Memory area 0 contains Data, Input, Output, Memory, Code, Persistent Data and Nonsafe Data: size: 1048576 bytes, highest used address: 445720, largest contiguous memory gap: 602856 bytes (57 %) 15 | Build complete -- 0 errors, 0 warnings : ready for download! 16 | Generate TMC information ... 17 | Import symbol information ... 18 | PLC.PLC : message: generate boot information... 19 | ------ Build started: Project: TwinCAT Project1, Configuration: Debug TwinCAT RT (x64) ------ 20 | ========== Build: 2 succeeded or up-to-date, 0 failed, 0 skipped ========== 21 | 22 | 23 | The following files were specified on the command line: 24 | 25 | 26 | 27 | These files could not be found and will not be loaded.------ Build started: Project: PLC, Configuration: Debug TwinCAT RT (x64) ------ 28 | ------ Build started: Application: TwinCAT_Project1.PLC ------- 29 | typify code ... 30 | generate code... 31 | generate global initializations ... 32 | generate code initialization ... 33 | generate relocations ... 34 | Size of generated code: 49528 bytes 35 | Size of global data: 11761 bytes 36 | Total allocated memory size for code and data: 445720 bytes 37 | Memory area 0 contains Data, Input, Output, Memory, Code, Persistent Data and Nonsafe Data: size: 1048576 bytes, highest used address: 445720, largest contiguous memory gap: 602856 bytes (57 %) 38 | Build complete -- 0 errors, 0 warnings : ready for download! 39 | Generate TMC information ... 40 | Import symbol information ... 41 | PLC.PLC : message: generate boot information... 42 | ------ Build started: Project: TwinCAT Project1, Configuration: Debug TwinCAT RT (x64) ------ 43 | ========== Build: 2 succeeded or up-to-date, 0 failed, 0 skipped ========== 44 | 45 | 46 | The following files were specified on the command line: 47 | 48 | 49 | 50 | These files could not be found and will not be loaded.------ Build started: Project: PLC, Configuration: Debug TwinCAT RT (x64) ------ 51 | ------ Build started: Application: TwinCAT_Project1.PLC ------- 52 | typify code ... 53 | generate code... 54 | generate global initializations ... 55 | generate code initialization ... 56 | generate relocations ... 57 | Size of generated code: 49528 bytes 58 | Size of global data: 11761 bytes 59 | Total allocated memory size for code and data: 445720 bytes 60 | Memory area 0 contains Data, Input, Output, Memory, Code, Persistent Data and Nonsafe Data: size: 1048576 bytes, highest used address: 445720, largest contiguous memory gap: 602856 bytes (57 %) 61 | Build complete -- 0 errors, 0 warnings : ready for download! 62 | Generate TMC information ... 63 | Import symbol information ... 64 | PLC.PLC : message: generate boot information... 65 | ------ Build started: Project: TwinCAT Project1, Configuration: Debug TwinCAT RT (x64) ------ 66 | ========== Build: 2 succeeded or up-to-date, 0 failed, 0 skipped ========== 67 | 68 | ------ Build started: Project: PLC, Configuration: Debug TwinCAT RT (x64) ------ 69 | ------ Build started: Application: TwinCAT_Project1.PLC ------- 70 | typify code ... 71 | generate code... 72 | generate global initializations ... 73 | generate code initialization ... 74 | generate relocations ... 75 | Size of generated code: 49528 bytes 76 | Size of global data: 11761 bytes 77 | Total allocated memory size for code and data: 445720 bytes 78 | Memory area 0 contains Data, Input, Output, Memory, Code, Persistent Data and Nonsafe Data: size: 1048576 bytes, highest used address: 445720, largest contiguous memory gap: 602856 bytes (57 %) 79 | Build complete -- 0 errors, 0 warnings : ready for download! 80 | Generate TMC information ... 81 | Import symbol information ... 82 | PLC.PLC : message: generate boot information... 83 | ------ Build started: Project: TwinCAT Project1, Configuration: Debug TwinCAT RT (x64) ------ 84 | ========== Build: 2 succeeded or up-to-date, 0 failed, 0 skipped ========== 85 | 86 | 87 | The following files were specified on the command line: 88 | 89 | 90 | 91 | These files could not be found and will not be loaded.------ Build started: Project: PLC, Configuration: Debug TwinCAT RT (x64) ------ 92 | ------ Build started: Application: TwinCAT_Project1.PLC ------- 93 | typify code ... 94 | generate code... 95 | generate global initializations ... 96 | generate code initialization ... 97 | generate relocations ... 98 | Size of generated code: 49528 bytes 99 | Size of global data: 11761 bytes 100 | Total allocated memory size for code and data: 445720 bytes 101 | Memory area 0 contains Data, Input, Output, Memory, Code, Persistent Data and Nonsafe Data: size: 1048576 bytes, highest used address: 445720, largest contiguous memory gap: 602856 bytes (57 %) 102 | Build complete -- 0 errors, 0 warnings : ready for download! 103 | Generate TMC information ... 104 | Import symbol information ... 105 | PLC.PLC : message: generate boot information... 106 | ------ Build started: Project: TwinCAT Project1, Configuration: Debug TwinCAT RT (x64) ------ 107 | ========== Build: 2 succeeded or up-to-date, 0 failed, 0 skipped ========== 108 | 109 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/TcProjectBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using TcBlackCLI; 4 | using Xunit; 5 | 6 | namespace TcBlackTests 7 | { 8 | public class TcProjectBuilderTests 9 | { 10 | private static readonly string currentDirectory = Environment.CurrentDirectory; 11 | private static readonly string testDirectory = Directory 12 | .GetParent(currentDirectory) 13 | .Parent.FullName; 14 | private static readonly string projectDirectory = Path.Combine( 15 | testDirectory, 16 | "..", 17 | "..", 18 | "twincat" 19 | ); 20 | 21 | [Fact] 22 | public void GetHashOfProjectWithHash() 23 | { 24 | var plcProject = new TcProjectBuilder( 25 | Path.Combine(projectDirectory, "WorkingProjectForUnitTests", "PLC", "PLC.plcproj") 26 | ); 27 | 28 | Assert.Equal("E7C52539-BBF0-7365-BEC4-14FF9FECC46D", plcProject.Hash); 29 | } 30 | 31 | [Fact] 32 | public void InitializeWithNonExistingPathRaiseException() 33 | { 34 | Assert.Throws( 35 | () => new TcProjectBuilder("Non/Existing/Path/PLC.plcproj") 36 | ); 37 | } 38 | 39 | [Fact] 40 | public void BuildMockBrokenProjectShouldRaiseException() 41 | { 42 | string brokenProjectPath = Path.Combine( 43 | projectDirectory, 44 | "BrokenProjectForUnitTests", 45 | "PLC2", 46 | "PLC2.plcproj" 47 | ); 48 | string failedBuildLogPath = Path.Combine( 49 | testDirectory, 50 | "TcProjectBuildTestData", 51 | "failedBuildWithExtraTextBelow.log" 52 | ); 53 | var plcProject = new MockTcProjectBuilder(brokenProjectPath, failedBuildLogPath); 54 | Assert.Throws(() => plcProject.Build(verbose: true)); 55 | } 56 | 57 | //// Only uncomment this if you want to test the real build process. 58 | //// Takes ~30 s to complete. 59 | //[Fact] 60 | //public void BuildRealBrokenProjectShouldRaiseException() 61 | //{ 62 | // string brokenPlcProjectPath = Path.Combine( 63 | // projectDirectory, "BrokenProjectForUnitTests", "PLC2", "PLC2.plcproj" 64 | // ); 65 | // var plcProject = new TcProjectBuilder(brokenPlcProjectPath); 66 | // Assert.Throws(() => plcProject.Build(verbose: true)); 67 | //} 68 | 69 | [Theory] 70 | [InlineData("PLC.plcproj")] 71 | [InlineData("Non/Existing/Path/PLC.plcproj")] 72 | public void TryGetHashOfNonExistingProject(string projectPath) 73 | { 74 | Assert.Throws(() => new TcProjectBuilder(projectPath)); 75 | } 76 | 77 | private static readonly string testDataDirectory = Path.Combine( 78 | testDirectory, 79 | "TcProjectBuildTestData" 80 | ); 81 | 82 | [Theory] 83 | [InlineData("succesfulBuild.log", false)] 84 | [InlineData("failedBuildWithExtraTextBelow.log", true)] 85 | [InlineData("firstBuildOkSecondBuildFailed.log", true)] 86 | public void CheckIfBuildFailedFromLogFile(string logFile, bool buildFailed) 87 | { 88 | string logFileContent = File.ReadAllText(Path.Combine(testDataDirectory, logFile)); 89 | bool actual = TcProjectBuilder.BuildFailed(logFileContent); 90 | 91 | Assert.Equal(buildFailed, actual); 92 | } 93 | 94 | private static readonly string workingProjectPouDirectory = Path.Combine( 95 | projectDirectory, 96 | "WorkingProjectForUnitTests", 97 | "PLC", 98 | "POUs" 99 | ); 100 | 101 | [Theory] 102 | [InlineData("Sum.TcPOU")] 103 | [InlineData("MAIN.TcPOU")] 104 | public void GetProjectHashFromSingleTcPouFilename(string filename) 105 | { 106 | string path = Path.Combine(workingProjectPouDirectory, filename); 107 | var plcProject = new TcProjectBuilder(path); 108 | 109 | Assert.Equal("E7C52539-BBF0-7365-BEC4-14FF9FECC46D", plcProject.Hash); 110 | } 111 | 112 | [Fact] 113 | public void TryToBuildProjectWithoutPlcprojFile() 114 | { 115 | string path = "C:/Program Files"; 116 | var exception = Assert.Throws(() => new TcProjectBuilder(path)); 117 | Assert.Equal( 118 | $"Unable to find a .plcproj file in any of the parent folders of " + $"{path}.", 119 | exception.Message 120 | ); 121 | } 122 | 123 | [Fact] 124 | public void TryToBuildProjectWithoutSlnFile() 125 | { 126 | string tempPlcProjFile = Path.Combine(projectDirectory, "../UnitTest.plcproj"); 127 | if (!File.Exists(tempPlcProjFile)) 128 | { 129 | File.Create(tempPlcProjFile).Close(); 130 | } 131 | var exception = Assert.Throws( 132 | () => new TcProjectBuilder(tempPlcProjFile) 133 | ); 134 | Assert.Equal( 135 | $"Unable to find a .sln file in any of the parent folders of " 136 | + $"{tempPlcProjFile}.", 137 | exception.Message 138 | ); 139 | File.Delete(tempPlcProjFile); 140 | } 141 | 142 | [Fact] 143 | public void FindOnlyExactExtensionForThreeCharacterExtensions() 144 | { 145 | string projectPath = Path.Combine( 146 | projectDirectory, 147 | "WorkingProjectForUnitTests", 148 | "PLC", 149 | "plcproj" 150 | ); 151 | var exception = Record.Exception(() => new TcProjectBuilder(projectPath)); 152 | Assert.Null(exception); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/TcBlackCLITests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/TcBlackCore/CodeLineBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | [assembly: CLSCompliant(true)] 4 | 5 | namespace TcBlackCore 6 | { 7 | public abstract class CodeLineBase 8 | { 9 | internal string unformattedCode; 10 | 11 | protected CodeLineBase(string unformattedCode) 12 | { 13 | this.unformattedCode = unformattedCode; 14 | } 15 | 16 | [System.Diagnostics.CodeAnalysis.SuppressMessage( 17 | "Microsoft.Design", 18 | "CA1045:DoNotPassTypesByReference", 19 | MessageId = "0#", 20 | Justification = "Don't know an alternative." 21 | )] 22 | public abstract string Format(ref int indents); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/TcBlackCore/CompositeCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace TcBlackCore 6 | { 7 | public class CompositeCode : CodeLineBase 8 | { 9 | private List codeLines; 10 | 11 | public CompositeCode(string unformattedCode) : base(unformattedCode) 12 | { 13 | codeLines = new List(); 14 | } 15 | 16 | /// 17 | /// Adds a new code line to the list. 18 | /// 19 | /// The code line to add. 20 | public void Add(CodeLineBase codeLine) 21 | { 22 | codeLines.Add(codeLine); 23 | } 24 | 25 | /// 26 | /// Format all the code. 27 | /// 28 | /// The number of indents. 29 | /// The formatted code. 30 | public override string Format(ref int indents) 31 | { 32 | Tokenize(); 33 | string formattedString = ""; 34 | foreach (CodeLineBase codeLine in codeLines) 35 | { 36 | formattedString += codeLine.Format(ref indents) + Globals.lineEnding; 37 | } 38 | 39 | return formattedString; 40 | } 41 | 42 | /// 43 | /// Assigns a specific type to each line. 44 | /// 45 | /// The CompositeStatement class itself. 46 | private CompositeCode Tokenize() 47 | { 48 | string lineEndingOfFile = unformattedCode.Contains("\r\n") ? "\r\n" : "\n"; 49 | string[] lines = unformattedCode.Split( 50 | new[] { lineEndingOfFile }, 51 | StringSplitOptions.None 52 | ); 53 | foreach (string line in lines) 54 | { 55 | if (line.Trim().Length == 0) 56 | { 57 | if ( 58 | codeLines.Count > 0 59 | && (codeLines.Last() is EmptyLine || codeLines.Last() is VariableBlockStart) 60 | ) 61 | { 62 | continue; 63 | } 64 | Add(new EmptyLine(unformattedCode: line)); 65 | } 66 | else if (line.StartsWith("END_VAR", StringComparison.OrdinalIgnoreCase)) 67 | { 68 | if (codeLines.Last() is VariableBlockStart) 69 | { 70 | codeLines.RemoveAt(codeLines.Count - 1); 71 | } 72 | else if (codeLines.Last() is EmptyLine) 73 | { 74 | codeLines.RemoveAt(codeLines.Count - 1); 75 | Add(new VariableBlockEnd(unformattedCode: line)); 76 | } 77 | else 78 | { 79 | Add(new VariableBlockEnd(unformattedCode: line)); 80 | } 81 | } 82 | else if (IsVariableBlockStart(line)) 83 | { 84 | TryRemoveLastEmptyLine(); 85 | Add(new VariableBlockStart(unformattedCode: line)); 86 | } 87 | else if ( 88 | line.StartsWith("FUNCTION", StringComparison.OrdinalIgnoreCase) 89 | || line.StartsWith("METHOD", StringComparison.OrdinalIgnoreCase) 90 | || line.StartsWith("PROPERTY", StringComparison.OrdinalIgnoreCase) 91 | || line.StartsWith("INTERFACE", StringComparison.OrdinalIgnoreCase) 92 | ) 93 | { 94 | Add(new ObjectDefinition(unformattedCode: line)); 95 | } 96 | else if (new VariableDeclaration(line).LooksLikeVariableDeclaration()) 97 | { 98 | Add(new VariableDeclaration(unformattedCode: line)); 99 | } 100 | else 101 | { 102 | Add(new UnknownCodeType(unformattedCode: line)); 103 | } 104 | } 105 | 106 | RemoveAllEmptyLinesAtTheEnd(); 107 | 108 | return this; 109 | } 110 | 111 | static private bool IsVariableBlockStart(string text) 112 | { 113 | string trimmedText = text.Trim().ToUpperInvariant(); 114 | 115 | return trimmedText == "VAR" 116 | || trimmedText.StartsWith("VAR_", StringComparison.OrdinalIgnoreCase); 117 | } 118 | 119 | /// 120 | /// Removes the last empty line from the list if it exists. 121 | /// 122 | private void TryRemoveLastEmptyLine() 123 | { 124 | try 125 | { 126 | if (codeLines.Last() is EmptyLine) 127 | { 128 | codeLines.RemoveAt(codeLines.Count - 1); 129 | } 130 | } 131 | catch (InvalidOperationException) { } 132 | } 133 | 134 | /// 135 | /// Removes all the empty lines which are in the end of the statement list. 136 | /// 137 | private void RemoveAllEmptyLinesAtTheEnd() 138 | { 139 | for (int i = codeLines.Count - 1; i >= 0; i--) 140 | { 141 | if (codeLines[i] is EmptyLine) 142 | { 143 | codeLines.RemoveAt(i); 144 | } 145 | else 146 | { 147 | break; 148 | } 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/TcBlackCore/EmptyLine.cs: -------------------------------------------------------------------------------- 1 | namespace TcBlackCore 2 | { 3 | public class EmptyLine : CodeLineBase 4 | { 5 | public EmptyLine(string unformattedCode) : base(unformattedCode) { } 6 | 7 | public override string Format(ref int indents) 8 | { 9 | return Globals.indentation.Repeat(indents); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/TcBlackCore/Globals.cs: -------------------------------------------------------------------------------- 1 | namespace TcBlackCore 2 | { 3 | public static class Globals 4 | { 5 | [System.Diagnostics.CodeAnalysis.SuppressMessage( 6 | "Microsoft.Usage", 7 | "CA2211: Non-constant fields should not be visible.", 8 | Justification = "Don't know better alternative." 9 | )] 10 | public static string lineEnding = ""; 11 | 12 | [System.Diagnostics.CodeAnalysis.SuppressMessage( 13 | "Microsoft.Usage", 14 | "CA2211: Non-constant fields should not be visible.", 15 | Justification = "Don't know better alternative." 16 | )] 17 | public static string indentation = ""; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/TcBlackCore/Keywords.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace TcBlackCore 5 | { 6 | static class Keywords 7 | { 8 | static MatchEvaluator KeywordsEval = new MatchEvaluator(UpText); 9 | static string[] KeywordList = 10 | { 11 | "abs", 12 | "acos", 13 | "add", 14 | "adr", 15 | "and", 16 | "andn", 17 | "any_date", 18 | "any_int", 19 | "any_num", 20 | "any_real", 21 | "any_sintrg", 22 | "any", 23 | "array", 24 | "asin", 25 | "at", 26 | "atan", 27 | "bitadr", 28 | "bool", 29 | "by", 30 | "byte", 31 | "cal", 32 | "calc", 33 | "calcn", 34 | "case", 35 | "constant", 36 | "cos", 37 | "date_and_time", 38 | "date", 39 | "dint", 40 | "div", 41 | "do", 42 | "dt", 43 | "dword", 44 | "else", 45 | "elsif", 46 | "end_case", 47 | "end_for", 48 | "end_if", 49 | "end_repeat", 50 | "end_struct", 51 | "end_type", 52 | "end_var", 53 | "end_while", 54 | "eq", 55 | "exit", 56 | "exp", 57 | "expt", 58 | "false", 59 | "for", 60 | "function_block", 61 | "function", 62 | "ge", 63 | "gt", 64 | "if", 65 | "indexof", 66 | "int", 67 | "jmp", 68 | "jmpc", 69 | "jmpcn", 70 | "ld", 71 | "ldn", 72 | "le", 73 | "lint", 74 | "ln", 75 | "log", 76 | "lreal", 77 | "lt", 78 | "ltime", 79 | "lword", 80 | "max", 81 | "method", 82 | "min", 83 | "mod", 84 | "move", 85 | "mul", 86 | "mux", 87 | "ne", 88 | "not", 89 | "of", 90 | "or", 91 | "orn", 92 | "params", 93 | "persistent", 94 | "pointer", 95 | "program", 96 | "r", 97 | "read_only", 98 | "read_write", 99 | "real", 100 | "reference", 101 | "repeat", 102 | "ret", 103 | "retain", 104 | "retc", 105 | "retcn", 106 | "return", 107 | "rol", 108 | "ror", 109 | "s", 110 | "sel", 111 | "shl", 112 | "shr", 113 | "sin", 114 | "sint", 115 | "sizeof", 116 | "sqrt", 117 | "st", 118 | "stn", 119 | "string", 120 | "struct", 121 | "sub", 122 | "super", 123 | "super", 124 | "tan", 125 | "then", 126 | "this", 127 | "time_of_day", 128 | "time", 129 | "to", 130 | "tod", 131 | "true", 132 | "trunc", 133 | "type", 134 | @"t#(?:\d+(?:ms|s|m|h|d))+", 135 | "udint", 136 | "uint", 137 | "ulint", 138 | "until", 139 | "usint", 140 | "uxint", 141 | "var_config", 142 | "var_external", 143 | "var_global", 144 | "var_in_out", 145 | "var_input", 146 | "var_output", 147 | "var_stat", 148 | "var_temp", 149 | "var", 150 | "while", 151 | "word", 152 | "wstring", 153 | "xint", 154 | "xor", 155 | "xorn", 156 | "xword", 157 | "pvoid", 158 | }; 159 | static Regex KeywordsRegex = new Regex( 160 | @"(? 9 | /// Format the definition of a FUNCTION, FUNCTION_BLOCK, etc. including the 10 | /// IMPLEMENTS and EXTENDS. 11 | /// 12 | public class ObjectDefinition : CodeLineBase 13 | { 14 | private struct TcObject 15 | { 16 | public TcObject( 17 | string objectType, 18 | string accessModifier, 19 | string name, 20 | string dataType, 21 | string implements, 22 | string extends 23 | ) 24 | { 25 | ObjectType = objectType; 26 | AccessModifier = accessModifier; 27 | Name = name; 28 | DataType = dataType; 29 | Extends = extends; 30 | Implements = implements; 31 | } 32 | 33 | public string ObjectType { get; } 34 | public string AccessModifier { get; } 35 | public string Name { get; } 36 | public string DataType { get; } 37 | public string Extends { get; } 38 | public string Implements { get; } 39 | } 40 | 41 | public ObjectDefinition(string unformattedCode) : base(unformattedCode) { } 42 | 43 | /// 44 | /// Return the formatted code. 45 | /// 46 | /// Number of indents to place in front. 47 | /// Formatted code. 48 | public override string Format(ref int indents) 49 | { 50 | TcObject tokens = Tokenize(); 51 | 52 | string formattedCode = 53 | Globals.indentation.Repeat(indents) 54 | + tokens.ObjectType 55 | + (tokens.AccessModifier.Length > 0 ? $" {tokens.AccessModifier}" : "") 56 | + $" {tokens.Name}" 57 | + (tokens.DataType.Length > 0 ? $" : {tokens.DataType}" : "") 58 | + (tokens.Extends.Length > 0 ? $" EXTENDS {tokens.Extends}" : "") 59 | + (tokens.Implements.Length > 0 ? $" IMPLEMENTS {tokens.Implements}" : ""); 60 | 61 | return formattedCode; 62 | } 63 | 64 | /// 65 | /// Return the split object definition. 66 | /// 67 | /// The split object defination. 68 | private TcObject Tokenize() 69 | { 70 | if (unformattedCode.Contains("FUNCTION_BLOCK")) 71 | { 72 | return TokenizeFunctionBlock(); 73 | } 74 | else if (unformattedCode.Contains("INTERFACE")) 75 | { 76 | return TokenizeInterface(); 77 | } 78 | else 79 | { 80 | return TokenizeMethodOrProperty(); 81 | } 82 | } 83 | 84 | private TcObject TokenizeInterface() 85 | { 86 | string pattern = @"INTERFACE\s+(\w+)\s*(?:EXTENDS((?:[\s,]+[\w\.]+)+))?"; 87 | 88 | MatchCollection matches = Regex.Matches( 89 | unformattedCode, 90 | pattern, 91 | RegexOptions.IgnoreCase 92 | ); 93 | if (matches.Count > 0) 94 | { 95 | Match match = matches[0]; 96 | string[] parents = Regex.Split(match.Groups[2].Value, @"[\s,]+"); 97 | return new TcObject( 98 | objectType: "INTERFACE", 99 | accessModifier: "", 100 | name: match.Groups[1].Value, 101 | dataType: "", 102 | extends: string.Join(", ", parents.Skip(1)), 103 | implements: "" 104 | ); 105 | } 106 | else 107 | { 108 | return new TcObject("", "", "", "", "", ""); 109 | } 110 | } 111 | 112 | private TcObject TokenizeFunctionBlock() 113 | { 114 | string[] splitDefinition = Regex 115 | .Split(unformattedCode, @",|\s+", RegexOptions.IgnorePatternWhitespace) 116 | .Where(s => !string.IsNullOrEmpty(s)) 117 | .ToArray(); 118 | 119 | List interfaces = new List(); 120 | List parents = new List(); 121 | List accessModifiers = new List(); 122 | bool implements = false; 123 | bool extends = false; 124 | foreach (string part in splitDefinition) 125 | { 126 | bool extendsStarts = (part.ToUpperInvariant() == "EXTENDS"); 127 | if (implements && !extendsStarts) 128 | { 129 | interfaces.Add(part); 130 | } 131 | 132 | bool implementsStarts = (part.ToUpperInvariant() == "IMPLEMENTS"); 133 | if (extends && !implementsStarts) 134 | { 135 | parents.Add(part); 136 | } 137 | 138 | if (implementsStarts) 139 | { 140 | implements = true; 141 | extends = false; 142 | } 143 | else if (extendsStarts) 144 | { 145 | extends = true; 146 | implements = false; 147 | } 148 | else if ( 149 | part.ToUpperInvariant() == "ABSTRACT" 150 | || part.ToUpperInvariant() == "FINAL" 151 | || part.ToUpperInvariant() == "INTERNAL" 152 | || part.ToUpperInvariant() == "PUBLIC" 153 | ) 154 | { 155 | accessModifiers.Add(part); 156 | } 157 | } 158 | string name = splitDefinition[1 + accessModifiers.Count]; 159 | 160 | return new TcObject( 161 | objectType: "FUNCTION_BLOCK", 162 | accessModifier: string.Join(" ", accessModifiers.ToArray()), 163 | name: name, 164 | dataType: "", 165 | extends: string.Join(", ", parents.ToArray()), 166 | implements: string.Join(", ", interfaces.ToArray()) 167 | ); 168 | } 169 | 170 | private TcObject TokenizeMethodOrProperty() 171 | { 172 | string entityType = @"\s*(FUNCTION|METHOD|PROPERTY)\s*"; 173 | string accessModifier = 174 | @"(PRIVATE|PUBLIC|PROTECTED|INTERNAL)?(?:(?: *)(FINAL|ABSTRACT))?\s*"; 175 | string name = @"(\w+)\s*:?"; 176 | string dataType = @"\s*(.*[^\s+;])?"; 177 | 178 | string pattern = $@"{entityType}{accessModifier}{name}{dataType}"; 179 | 180 | MatchCollection matches = Regex.Matches(unformattedCode, pattern); 181 | if (matches.Count > 0) 182 | { 183 | Match match = matches[0]; 184 | bool twoModifers = 185 | match.Groups[2].Value.Length > 0 && match.Groups[3].Value.Length > 0; 186 | return new TcObject( 187 | objectType: match.Groups[1].Value, 188 | accessModifier: match.Groups[2].Value 189 | + (twoModifers ? " " : "") 190 | + match.Groups[3].Value, 191 | name: match.Groups[4].Value, 192 | dataType: Keywords.Upper(match.Groups[5].Value), 193 | extends: "", 194 | implements: "" 195 | ); 196 | } 197 | else 198 | { 199 | return new TcObject("", "", "", "", "", ""); 200 | } 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/TcBlackCore/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TcBlackCore")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("TcBlackCore")] 12 | [assembly: AssemblyCopyright("Copyright © 2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("434c7a68-c27f-4f46-977e-2f60441feb64")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("0.3.0.0")] 35 | -------------------------------------------------------------------------------- /src/TcBlackCore/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace TcBlackCore 5 | { 6 | /// 7 | /// Source: https://stackoverflow.com/a/47915552/6329629 8 | /// 9 | public static class StringExtensions 10 | { 11 | public static string Repeat(this string text, int times) 12 | { 13 | if (text == null) 14 | { 15 | throw new ArgumentNullException("text"); 16 | } 17 | 18 | string _repeatedString = new StringBuilder(text.Length * times) 19 | .Insert(0, text, times) 20 | .ToString(); 21 | 22 | return _repeatedString; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/TcBlackCore/TcBlackCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {434C7A68-C27F-4F46-977E-2F60441FEB64} 8 | Library 9 | Properties 10 | TcBlackCore 11 | TcBlackCore 12 | v4.8 13 | 512 14 | true 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | ..\TcBlack.ruleset 26 | true 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | ..\TcBlack.ruleset 36 | true 37 | 38 | 39 | false 40 | 41 | 42 | TcBlackCoreSign.pfx 43 | 44 | 45 | TcBlack_icon.ico 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/TcBlackCore/TcBlack_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/src/TcBlackCore/TcBlack_icon.ico -------------------------------------------------------------------------------- /src/TcBlackCore/TcDeclaration.cs: -------------------------------------------------------------------------------- 1 | namespace TcBlackCore 2 | { 3 | public struct TcDeclaration 4 | { 5 | public TcDeclaration( 6 | string name, 7 | string allocation, 8 | string dataType, 9 | string initialization, 10 | string comment 11 | ) 12 | { 13 | Name = name; 14 | Allocation = allocation; 15 | DataType = dataType; 16 | Initialization = initialization; 17 | Comment = comment; 18 | } 19 | 20 | public override bool Equals(object obj) 21 | { 22 | if (obj == null || GetType() != obj.GetType()) 23 | { 24 | return false; 25 | } 26 | 27 | var other = (TcDeclaration)obj; 28 | return Name == other.Name 29 | && Allocation == other.Allocation 30 | && DataType == other.DataType 31 | && Initialization == other.Initialization 32 | && Comment == other.Comment; 33 | } 34 | 35 | public override int GetHashCode() 36 | { 37 | return base.GetHashCode(); 38 | } 39 | 40 | public static bool operator ==(TcDeclaration obj1, TcDeclaration obj2) 41 | { 42 | if (obj1 == null) 43 | { 44 | return obj2 == null; 45 | } 46 | 47 | return obj1.Equals(obj2); 48 | } 49 | 50 | public static bool operator !=(TcDeclaration obj1, TcDeclaration obj2) 51 | { 52 | return !(obj1 == obj2); 53 | } 54 | 55 | public string Name { get; } 56 | public string Allocation { get; } 57 | public string DataType { get; } 58 | public string Initialization { get; } 59 | public string Comment { get; } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/TcBlackCore/UnknownCodeType.cs: -------------------------------------------------------------------------------- 1 | namespace TcBlackCore 2 | { 3 | public class UnknownCodeType : CodeLineBase 4 | { 5 | public UnknownCodeType(string unformattedCode) : base(unformattedCode) { } 6 | 7 | public override string Format(ref int indents) 8 | { 9 | return unformattedCode; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/TcBlackCore/VariableBlockEnd.cs: -------------------------------------------------------------------------------- 1 | namespace TcBlackCore 2 | { 3 | public class VariableBlockEnd : CodeLineBase 4 | { 5 | public VariableBlockEnd(string unformattedCode) : base(unformattedCode) { } 6 | 7 | public override string Format(ref int indents) 8 | { 9 | indents = (indents == 0) ? 0 : indents -= 1; 10 | 11 | string formattedCode = Globals.indentation.Repeat(indents) + unformattedCode.Trim(); 12 | 13 | return formattedCode; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/TcBlackCore/VariableBlockStart.cs: -------------------------------------------------------------------------------- 1 | namespace TcBlackCore 2 | { 3 | public class VariableBlockStart : CodeLineBase 4 | { 5 | public VariableBlockStart(string unformattedCode) : base(unformattedCode) { } 6 | 7 | public override string Format(ref int indents) 8 | { 9 | string formattedCode = Globals.indentation.Repeat(indents) + unformattedCode.Trim(); 10 | indents += 1; 11 | 12 | return formattedCode; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/TcBlackCore/VariableDeclaration.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace TcBlackCore 4 | { 5 | public class VariableDeclaration : CodeLineBase 6 | { 7 | public VariableDeclaration(string unformattedCode) : base(unformattedCode) { } 8 | 9 | public override string Format(ref int indents) 10 | { 11 | TcDeclaration tokens = Tokenize(); 12 | string formattedDatatype = ( 13 | InsertSpacesAroundOperators(tokens.DataType).Replace(",", ", ") 14 | ); 15 | 16 | string formattedCode = ( 17 | Globals.indentation.Repeat(indents) 18 | + tokens.Name 19 | + (tokens.Allocation.Length > 0 ? $" AT {tokens.Allocation}" : "") 20 | + $" : {formattedDatatype}" 21 | + ( 22 | tokens.Initialization.Length > 0 23 | ? $" := {tokens.Initialization.Replace(",", ", ")}" 24 | : "" 25 | ) 26 | + ";" 27 | + (tokens.Comment.Length > 0 ? $" {tokens.Comment}" : "") 28 | ); 29 | 30 | return formattedCode; 31 | } 32 | 33 | private TcDeclaration Tokenize() 34 | { 35 | string variable_pattern = @"^\s*(\w+)\s*"; 36 | string address_pattern = @"(?:AT\s+)?([\w+%.*]*)?\s*"; 37 | string array_pattern = @"ARRAY\[.*\]\s+OF\s+[\w.]+"; 38 | string unit_pattern = 39 | $@"({array_pattern}\(.*\)|{array_pattern}\[.*\]|{array_pattern}" 40 | + @"|\w+\(.*\)|\w+\[.*\]|[^;:]*)\s*"; 41 | string initialization = $@"(?::=)?(?s)\s*(.*?)?"; 42 | string comment = $@"\s*(\/\/[^\n]+|\(\*.*?\*\))?"; 43 | string pattern = 44 | $@"{variable_pattern}{address_pattern}:\s*" 45 | + $@"{unit_pattern}{initialization};{comment}"; 46 | 47 | string strInitRegex = $@"([""'])(?:(?=(\$?))\2.)*?\1(?=\s*;)"; 48 | 49 | Match match = Regex.Match(unformattedCode, strInitRegex); 50 | string strInit = ""; 51 | if (match.Length > 0) 52 | { 53 | strInit = match.Groups[0].Value; 54 | unformattedCode = Regex.Replace(unformattedCode, strInitRegex, ""); 55 | } 56 | 57 | MatchCollection matches = Regex.Matches( 58 | unformattedCode, 59 | pattern, 60 | RegexOptions.IgnoreCase 61 | ); 62 | TcDeclaration variable; 63 | if (matches.Count > 0) 64 | { 65 | match = matches[0]; 66 | if (strInit.Length == 0) 67 | { 68 | strInit = Keywords.Upper(RemoveWhiteSpaceIfPossible(match.Groups[4].Value)); 69 | } 70 | variable = new TcDeclaration( 71 | name: RemoveWhiteSpaceIfPossible(match.Groups[1].Value), 72 | allocation: RemoveWhiteSpaceIfPossible(match.Groups[2].Value), 73 | dataType: Keywords.Upper(RemoveWhiteSpaceIfPossible(match.Groups[3].Value)), 74 | initialization: strInit, 75 | comment: match.Groups[5].Value.Trim() 76 | ); 77 | } 78 | else 79 | { 80 | variable = new TcDeclaration("", "", "", "", ""); 81 | } 82 | 83 | return variable; 84 | } 85 | 86 | /// 87 | /// Tokenizes the declaration to check if it finds a variable name. 88 | /// 89 | /// Code line to inspect 90 | /// Returns true if it thinks the code is a declaration. 91 | public bool LooksLikeVariableDeclaration() 92 | { 93 | var declaration = new VariableDeclaration(unformattedCode).Tokenize(); 94 | 95 | return !string.IsNullOrEmpty(declaration.Name); 96 | } 97 | 98 | /// 99 | /// Removes spaces between square and round brackets, except if it is a string. 100 | /// 101 | /// The string to remove spaces from. 102 | /// Cleaned up string. 103 | /// source: https://stackoverflow.com/a/63486599/6329629 104 | private static string RemoveWhiteSpaceIfPossible(string str) 105 | { 106 | string pattern = ( 107 | "\\s+(?=[^[]*\\])" 108 | + "|(? 116 | /// Return string with single spaces around +, -, * and / operators. 117 | /// 118 | /// 119 | /// "a+b" => "a + b" 120 | /// 121 | private static string InsertSpacesAroundOperators(string unformatted) 122 | { 123 | return Regex.Replace(unformatted, @"(?<=\w|\))([-+\/*])", " $1 "); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/TcBlackCoreTests/CompositeCodeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using TcBlackCore; 3 | using Xunit; 4 | 5 | [assembly: CLSCompliant(false)] 6 | 7 | namespace TcBlackTests 8 | { 9 | [Collection("Sequential")] 10 | public class CompositeCodeTests 11 | { 12 | [Fact] 13 | public void FormatDeclaration() 14 | { 15 | Globals.indentation = " "; 16 | Globals.lineEnding = "\n"; 17 | string unformattedCode = 18 | "// Single line comments are also not formatted, yet\n" 19 | + "FUNCTION AddIntegers:DINT\n" 20 | + "VAR_INPUT\n" 21 | + "var1: LREAL :=9.81 ; // Comment\n" 22 | + "var2 AT %Q*: BOOL ; \n" 23 | + "\n\n\n" 24 | + " {attribute 'hide'}\n" 25 | + "anotherBool : BOOL:=TRUE;\n" 26 | + "END_VAR\n\n\n"; 27 | 28 | int indents = 0; 29 | string actual = new CompositeCode(unformattedCode: unformattedCode).Format(ref indents); 30 | string expected = 31 | "// Single line comments are also not formatted, yet\n" 32 | + "FUNCTION AddIntegers : DINT\n" 33 | + "VAR_INPUT\n" 34 | + " var1 : LREAL := 9.81; // Comment\n" 35 | + " var2 AT %Q* : BOOL;\n" 36 | + " \n" 37 | + " {attribute 'hide'}\n" 38 | + " anotherBool : BOOL := TRUE;\n" 39 | + "END_VAR\n"; 40 | 41 | Assert.Equal(expected, actual); 42 | } 43 | 44 | [Fact] 45 | public void FormatEmptyDeclaration() 46 | { 47 | Globals.indentation = " "; 48 | Globals.lineEnding = "\n"; 49 | string unformattedCode = ""; 50 | int indents = 0; 51 | string actual = new CompositeCode(unformattedCode: unformattedCode).Format(ref indents); 52 | 53 | string expected = ""; 54 | Assert.Equal(expected, actual); 55 | } 56 | 57 | [Fact] 58 | public void RemoveEmptyVariableTypeDeclarations() 59 | { 60 | Globals.indentation = " "; 61 | Globals.lineEnding = "\n"; 62 | string unformattedCode = 63 | "FUNCTION_BLOCK Something\n" 64 | + "VAR_INPUT\n" 65 | + "END_VAR\n" 66 | + "VAR_OUTPUT\n" 67 | + "END_VAR\n" 68 | + "VAR\n" 69 | + " isThatTrue : BOOL;\n" 70 | + "END_VAR"; 71 | int indents = 0; 72 | string actual = new CompositeCode(unformattedCode: unformattedCode).Format(ref indents); 73 | 74 | string expected = 75 | "FUNCTION_BLOCK Something\n" + "VAR\n" + " isThatTrue : BOOL;\n" + "END_VAR\n"; 76 | Assert.Equal(expected, actual); 77 | } 78 | 79 | [Fact] 80 | public void RemoveEmptyLinesBeforeAndAfterVarBlockStartAndEnd() 81 | { 82 | Globals.indentation = " "; 83 | Globals.lineEnding = "\n"; 84 | string unformattedCode = 85 | "FUNCTION Abx\n" 86 | + "\n" 87 | + "VAR_INPUT\n" 88 | + "\n" 89 | + " number : INT;\n" 90 | + "\n\n" 91 | + "END_VAR\n" 92 | + "\n" 93 | + "VAR\n" 94 | + "\n\n" 95 | + " someVar : BOOL;\n" 96 | + "\n" 97 | + "END_VAR\n"; 98 | int indents = 0; 99 | string actual = new CompositeCode(unformattedCode: unformattedCode).Format(ref indents); 100 | 101 | string expected = 102 | "FUNCTION Abx\n" 103 | + "VAR_INPUT\n" 104 | + " number : INT;\n" 105 | + "END_VAR\n" 106 | + "VAR\n" 107 | + " someVar : BOOL;\n" 108 | + "END_VAR\n"; 109 | Assert.Equal(expected, actual); 110 | } 111 | 112 | [Fact] 113 | public void CommentWithoutASpace() 114 | { 115 | Globals.indentation = " "; 116 | Globals.lineEnding = "\n"; 117 | string unformattedCode = " //Some : FB_Some;\n"; 118 | int indents = 0; 119 | string actual = new CompositeCode(unformattedCode: unformattedCode).Format(ref indents); 120 | 121 | string expected = " //Some : FB_Some;\n"; 122 | Assert.Equal(expected, actual); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/TcBlackCoreTests/EmptyLineTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using TcBlackCore; 3 | 4 | namespace TcBlackTests 5 | { 6 | [Collection("Sequential")] 7 | public class EmptyLineTests 8 | { 9 | [Theory] 10 | [InlineData("", 0, "")] 11 | [InlineData("\t\t", 1, " ")] 12 | [InlineData("\t ", 2, " ")] 13 | public void DifferentEmptyLines(string unformattedCode, int initialIndents, string expected) 14 | { 15 | Globals.indentation = " "; 16 | Globals.lineEnding = "\n"; 17 | EmptyLine line = new EmptyLine(unformattedCode); 18 | 19 | int indents = initialIndents; 20 | Assert.Equal(expected, line.Format(ref indents)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/TcBlackCoreTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TcBlackCoreTests")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("TcBlackCoreTests")] 12 | [assembly: AssemblyCopyright("Copyright © 2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("87dfda93-482c-4c04-abf1-50e23f24f85d")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("0.2.0.0")] 35 | -------------------------------------------------------------------------------- /src/TcBlackCoreTests/TcBlackCoreTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Debug 9 | AnyCPU 10 | {D86EFF5A-952E-42BD-AEB4-605F02ED336C} 11 | Library 12 | Properties 13 | TcBlackCoreTests 14 | TcBlackCoreTests 15 | v4.8 16 | 512 17 | true 18 | 19 | 20 | 21 | 22 | 23 | true 24 | full 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | prompt 29 | 4 30 | ..\TcBlackTests.ruleset 31 | true 32 | 33 | 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | ..\TcBlackTests.ruleset 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll 54 | 55 | 56 | ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll 57 | 58 | 59 | ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll 60 | 61 | 62 | ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Designer 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | {434c7a68-c27f-4f46-977e-2f60441feb64} 86 | TcBlackCore 87 | 88 | 89 | 90 | 91 | 92 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/TcBlackCoreTests/UnknownCodeTypeTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using TcBlackCore; 3 | 4 | namespace TcBlackTests 5 | { 6 | [Collection("Sequential")] 7 | public class UnknownCodeTypeTests 8 | { 9 | [Theory] 10 | [InlineData("some gibberisch")] 11 | [InlineData("// Or some comment")] 12 | [InlineData("\t (* with some spaces *)")] 13 | [InlineData(" {attribute 'hide'}")] 14 | public void DifferentEmptyLines(string unformattedCode) 15 | { 16 | Globals.indentation = " "; 17 | Globals.lineEnding = "\n"; 18 | var line = new UnknownCodeType(unformattedCode); 19 | 20 | int indents = 0; 21 | Assert.Equal(unformattedCode, line.Format(ref indents)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/TcBlackCoreTests/VarBlockEndTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using TcBlackCore; 3 | 4 | namespace TcBlackTests 5 | { 6 | [Collection("Sequential")] 7 | public class VarBlockEndTests 8 | { 9 | [Theory] 10 | [InlineData(" END_VAR", 1, "END_VAR", 0)] 11 | [InlineData(" END_VAR ", 2, " END_VAR", 1)] 12 | [InlineData(" END_VAR ", 0, "END_VAR", 0)] 13 | public void DifferentIndents( 14 | string originalCode, 15 | int indents, 16 | string expectedCode, 17 | int expectedIndents 18 | ) 19 | { 20 | Globals.indentation = " "; 21 | Globals.lineEnding = "\n"; 22 | VariableBlockEnd var = new VariableBlockEnd(originalCode); 23 | Assert.Equal(expectedCode, var.Format(ref indents)); 24 | Assert.Equal(expectedIndents, indents); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/TcBlackCoreTests/VarBlockStartTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using TcBlackCore; 3 | 4 | namespace TcBlackTests 5 | { 6 | [Collection("Sequential")] 7 | public class VarBlockStartTests 8 | { 9 | [Theory] 10 | [InlineData(" VAR", 0, "VAR", 1)] 11 | [InlineData(" VAR_INPUT ", 1, " VAR_INPUT", 2)] 12 | public void DifferentIndents( 13 | string originalCode, 14 | int indents, 15 | string expectedCode, 16 | int expectedIndents 17 | ) 18 | { 19 | Globals.indentation = " "; 20 | Globals.lineEnding = "\n"; 21 | VariableBlockStart var = new VariableBlockStart(originalCode); 22 | Assert.Equal(expectedCode, var.Format(ref indents)); 23 | Assert.Equal(expectedIndents, indents); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/TcBlackCoreTests/VariableDeclarationTests.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/src/TcBlackCoreTests/VariableDeclarationTests.cs -------------------------------------------------------------------------------- /src/TcBlackCoreTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/TcBlackTests.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/fileForUnitTest.slnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/src/fileForUnitTest.slnx -------------------------------------------------------------------------------- /tcblack_extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/tcblack_extension.gif -------------------------------------------------------------------------------- /twincat/BrokenProjectForUnitTests/BrokenProjectForUnitTests.tspproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /twincat/BrokenProjectForUnitTests/PLC2/PLC2.plcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0.0 4 | 2.0 5 | {0ab6833f-593b-4e68-ac63-b575821acf23} 6 | True 7 | true 8 | true 9 | false 10 | PLC2 11 | 3.1.4023.0 12 | {0c130361-37c7-4514-bcc5-c23776fca6f0} 13 | {ba85972a-0ec9-4694-b715-949a4d2eae95} 14 | {c8ee4393-4bdc-4dc8-bf00-db0258f8fe3b} 15 | {ce662da8-8edf-476d-8f7a-ad419fd5d057} 16 | {517df8b0-7da8-4e29-ba26-ea2cf9106d24} 17 | {c01f7efe-8be4-4b51-9b88-ba98ea58f9c4} 18 | false 19 | 20 | 21 | 22 | Code 23 | 24 | 25 | Code 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Tc2_Standard, * (Beckhoff Automation GmbH) 37 | Tc2_Standard 38 | 39 | 40 | Tc2_System, * (Beckhoff Automation GmbH) 41 | Tc2_System 42 | 43 | 44 | Tc3_Module, * (Beckhoff Automation GmbH) 45 | Tc3_Module 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | "<ProjectRoot>" 54 | 55 | {192FAD59-8248-4824-A8DE-9177C94C195A} 56 | 57 | "{192FAD59-8248-4824-A8DE-9177C94C195A}" 58 | 59 | 60 | 61 | {246001F4-279D-43AC-B241-948EB31120E1} 62 | 63 | "{246001F4-279D-43AC-B241-948EB31120E1}" 64 | 65 | 66 | GlobalVisuImageFilePath 67 | %APPLICATIONPATH% 68 | 69 | 70 | {29BD8D0C-3586-4548-BB48-497B9A01693F} 71 | 72 | "{29BD8D0C-3586-4548-BB48-497B9A01693F}" 73 | 74 | Rules 75 | 76 | "Rules" 77 | 78 | 79 | 80 | 81 | 82 | 83 | {F66C7017-BDD8-4114-926C-81D6D687E35F} 84 | 85 | "{F66C7017-BDD8-4114-926C-81D6D687E35F}" 86 | 87 | 88 | 89 | {40450F57-0AA3-4216-96F3-5444ECB29763} 90 | 91 | "{40450F57-0AA3-4216-96F3-5444ECB29763}" 92 | 93 | 94 | ActiveVisuProfile 95 | IR0whWr8bwfwBwAAiD2qpQAAAABVAgAA37x72QAAAAABAAAAAAAAAAEaUwB5AHMAdABlAG0ALgBTAHQAcgBpAG4AZwACTHsAZgA5ADUAYgBiADQAMgA2AC0ANQA1ADIANAAtADQAYgA0ADUALQA5ADQAMAAwAC0AZgBiADAAZgAyAGUANwA3AGUANQAxAGIAfQADCE4AYQBtAGUABDBUAHcAaQBuAEMAQQBUACAAMwAuADEAIABCAHUAaQBsAGQAIAA0ADAAMgA0AC4ANwAFFlAAcgBvAGYAaQBsAGUARABhAHQAYQAGTHsAMQA2AGUANQA1AGIANgAwAC0ANwAwADQAMwAtADQAYQA2ADMALQBiADYANQBiAC0ANgAxADQANwAxADMAOAA3ADgAZAA0ADIAfQAHEkwAaQBiAHIAYQByAGkAZQBzAAhMewAzAGIAZgBkADUANAA1ADkALQBiADAANwBmAC0ANABkADYAZQAtAGEAZQAxAGEALQBhADgAMwAzADUANgBhADUANQAxADQAMgB9AAlMewA5AGMAOQA1ADgAOQA2ADgALQAyAGMAOAA1AC0ANAAxAGIAYgAtADgAOAA3ADEALQA4ADkANQBmAGYAMQBmAGUAZABlADEAYQB9AAoOVgBlAHIAcwBpAG8AbgALBmkAbgB0AAwKVQBzAGEAZwBlAA0KVABpAHQAbABlAA4aVgBpAHMAdQBFAGwAZQBtAE0AZQB0AGUAcgAPDkMAbwBtAHAAYQBuAHkAEAxTAHkAcwB0AGUAbQARElYAaQBzAHUARQBsAGUAbQBzABIwVgBpAHMAdQBFAGwAZQBtAHMAUwBwAGUAYwBpAGEAbABDAG8AbgB0AHIAbwBsAHMAEyhWAGkAcwB1AEUAbABlAG0AcwBXAGkAbgBDAG8AbgB0AHIAbwBsAHMAFCRWAGkAcwB1AEUAbABlAG0AVABlAHgAdABFAGQAaQB0AG8AcgAVIlYAaQBzAHUATgBhAHQAaQB2AGUAQwBvAG4AdAByAG8AbAAWFHYAaQBzAHUAaQBuAHAAdQB0AHMAFwxzAHkAcwB0AGUAbQAYGFYAaQBzAHUARQBsAGUAbQBCAGEAcwBlABkmRABlAHYAUABsAGEAYwBlAGgAbwBsAGQAZQByAHMAVQBzAGUAZAAaCGIAbwBvAGwAGyJQAGwAdQBnAGkAbgBDAG8AbgBzAHQAcgBhAGkAbgB0AHMAHEx7ADQAMwBkADUAMgBiAGMAZQAtADkANAAyAGMALQA0ADQAZAA3AC0AOQBlADkANAAtADEAYgBmAGQAZgAzADEAMABlADYAMwBjAH0AHRxBAHQATABlAGEAcwB0AFYAZQByAHMAaQBvAG4AHhRQAGwAdQBnAGkAbgBHAHUAaQBkAB8WUwB5AHMAdABlAG0ALgBHAHUAaQBkACBIYQBmAGMAZAA1ADQANAA2AC0ANAA5ADEANAAtADQAZgBlADcALQBiAGIANwA4AC0AOQBiAGYAZgBlAGIANwAwAGYAZAAxADcAIRRVAHAAZABhAHQAZQBJAG4AZgBvACJMewBiADAAMwAzADYANgBhADgALQBiADUAYwAwAC0ANABiADkAYQAtAGEAMAAwAGUALQBlAGIAOAA2ADAAMQAxADEAMAA0AGMAMwB9ACMOVQBwAGQAYQB0AGUAcwAkTHsAMQA4ADYAOABmAGYAYwA5AC0AZQA0AGYAYwAtADQANQAzADIALQBhAGMAMAA2AC0AMQBlADMAOQBiAGIANQA1ADcAYgA2ADkAfQAlTHsAYQA1AGIAZAA0ADgAYwAzAC0AMABkADEANwAtADQAMQBiADUALQBiADEANgA0AC0ANQBmAGMANgBhAGQAMgBiADkANgBiADcAfQAmFk8AYgBqAGUAYwB0AHMAVAB5AHAAZQAnVFUAcABkAGEAdABlAEwAYQBuAGcAdQBhAGcAZQBNAG8AZABlAGwARgBvAHIAQwBvAG4AdgBlAHIAdABpAGIAbABlAEwAaQBiAHIAYQByAGkAZQBzACgQTABpAGIAVABpAHQAbABlACkUTABpAGIAQwBvAG0AcABhAG4AeQAqHlUAcABkAGEAdABlAFAAcgBvAHYAaQBkAGUAcgBzACs4UwB5AHMAdABlAG0ALgBDAG8AbABsAGUAYwB0AGkAbwBuAHMALgBIAGEAcwBoAHQAYQBiAGwAZQAsEnYAaQBzAHUAZQBsAGUAbQBzAC1INgBjAGIAMQBjAGQAZQAxAC0AZAA1AGQAYwAtADQAYQAzAGIALQA5ADAANQA0AC0AMgAxAGYAYQA3ADUANgBhADMAZgBhADQALihJAG4AdABlAHIAZgBhAGMAZQBWAGUAcgBzAGkAbwBuAEkAbgBmAG8AL0x7AGMANgAxADEAZQA0ADAAMAAtADcAZgBiADkALQA0AGMAMwA1AC0AYgA5AGEAYwAtADQAZQAzADEANABiADUAOQA5ADYANAAzAH0AMBhNAGEAagBvAHIAVgBlAHIAcwBpAG8AbgAxGE0AaQBuAG8AcgBWAGUAcgBzAGkAbwBuADIMTABlAGcAYQBjAHkAMzBMAGEAbgBnAHUAYQBnAGUATQBvAGQAZQBsAFYAZQByAHMAaQBvAG4ASQBuAGYAbwA0MEwAbwBhAGQATABpAGIAcgBhAHIAaQBlAHMASQBuAHQAbwBQAHIAbwBqAGUAYwB0ADUaQwBvAG0AcABhAHQAaQBiAGkAbABpAHQAeQDQAAIaA9ADAS0E0AUGGgfQBwgaAUUHCQjQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtDtAPAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60BAAAA0A0BLRHQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0S0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAUAAAA0AwLrQIAAADQDQEtE9APAS0Q0AAJGgRFCgsEAwAAAAUAAAANAAAAAAAAANAMC60CAAAA0A0BLRTQDwEtENAACRoERQoLBAMAAAAFAAAADQAAAAAAAADQDAutAgAAANANAS0V0A8BLRDQAAkaBEUKCwQDAAAABQAAAA0AAAAAAAAA0AwLrQIAAADQDQEtFtAPAS0X0AAJGgRFCgsEAwAAAAUAAAANAAAAKAAAANAMC60EAAAA0A0BLRjQDwEtENAZGq0BRRscAdAAHBoCRR0LBAMAAAAFAAAADQAAAAAAAADQHh8tINAhIhoCRSMkAtAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAAAAANADAS0n0CgBLRHQKQEtENAAJRoFRQoLBAMAAAADAAAAAAAAAAoAAADQJgutAQAAANADAS0n0CgBLRHQKQEtEJoqKwFFAAEC0AABLSzQAAEtF9AAHy0t0C4vGgPQMAutAQAAANAxC60XAAAA0DIarQDQMy8aA9AwC60CAAAA0DELrQMAAADQMhqtANA0Gq0A0DUarQA= 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | System.Collections.Hashtable 104 | {54dd0eac-a6d8-46f2-8c27-2f43c7e49861} 105 | System.String 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /twincat/BrokenProjectForUnitTests/PLC2/POUs/MAIN.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /twincat/BrokenProjectForUnitTests/PLC2/PlcTask.TcTTO: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 10000 6 | 20 7 | 8 | MAIN 9 | 10 | {d70b8b6e-3b2b-492f-8102-71367dce85a5} 11 | {5175b203-1163-448e-800b-7ba3389b2260} 12 | {1fdf64f1-01fe-478f-9bd8-3fd8669de7af} 13 | {639d8d4c-0858-485d-b759-c7fa83dfb64b} 14 | {e33adbe9-07b6-44e7-92a8-1f7e73f4743e} 15 | 16 | 17 | -------------------------------------------------------------------------------- /twincat/ShowcaseProject/PLC3/POUs/FB_Base.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /twincat/ShowcaseProject/PLC3/POUs/FB_Child.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 17 | 18 | 2 THEN 24 | Counter := Counter + 5 ; 25 | END_IF 26 | END_IF 27 | 28 | Base(Variable1:=2, Variable2:=3 , Variable3:= 5,Sentence:='', Conditions :=Conditions); 29 | 30 | 31 | AddTwoInts( Variable1 :=4, 32 | Variable2:=4);]]> 33 | 34 | 35 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /twincat/ShowcaseProject/PLC3/POUs/I_Interface.TcIO: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /twincat/ShowcaseProject/PLC3/POUs/I_Interface2.TcIO: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /twincat/ShowcaseProject/PLC3/POUs/MAIN.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /twincat/ShowcaseProject/PLC3/PlcTask.TcTTO: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 10000 6 | 20 7 | 8 | MAIN 9 | 10 | {1f8d73b8-6e3a-4792-bd9a-bccf3231bd58} 11 | {2b4cc4b4-f288-4607-b119-1bc30d82c05d} 12 | {4db9b352-74b0-4ee9-920d-4380b1e96447} 13 | {9a53e4d8-5be5-4c21-a504-e979a6b55925} 14 | {060ca887-d2d2-4b1a-8279-5f8f42d64ede} 15 | 16 | 17 | -------------------------------------------------------------------------------- /twincat/ShowcaseProject/ShowcaseProject.tsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PlcTask 8 | 9 | 10 | 11 | 12 | 13 | 14 | PLC3 Instance 15 | {08500001-0000-0000-F000-000000000064} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /twincat/WorkingProjectForUnitTests/PLC/POUs/MAIN.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /twincat/WorkingProjectForUnitTests/PLC/POUs/Sum.TcPOU: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /twincat/WorkingProjectForUnitTests/PLC/PlcTask.TcTTO: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 10000 6 | 20 7 | 8 | MAIN 9 | 10 | {b6bfd5be-508c-4040-a3b6-0a5d355ce648} 11 | {399daf20-f708-41c4-8c8f-9e85b77f2702} 12 | {1ce90658-d43e-4c82-876a-aa934ef8cbfd} 13 | {1094b471-37bc-472d-908f-8846335482ed} 14 | {800783d8-933a-42a2-b402-8735b7e4be77} 15 | 16 | 17 | -------------------------------------------------------------------------------- /twincat/WorkingProjectForUnitTests/PLC/_CompileInfo/E7C52539-BBF0-7365-BEC4-14FF9FECC46D.compileinfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Roald87/TcBlack/10222591a4605f4cbcc7935a1da73e0b49e6ffc6/twincat/WorkingProjectForUnitTests/PLC/_CompileInfo/E7C52539-BBF0-7365-BEC4-14FF9FECC46D.compileinfo -------------------------------------------------------------------------------- /twincat/WorkingProjectForUnitTests/WorkingProjectForUnitTests.tspproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | --------------------------------------------------------------------------------