├── .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 | 
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 | 
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 |
140 |
--------------------------------------------------------------------------------
/logo/TcBlack_icon_square.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
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 |
--------------------------------------------------------------------------------