WIP: allow using nff as a diagnostics source #1
					 1 changed files with 366 additions and 8 deletions
				
			
		docs: update README with new features
				commit
				
					
					
						92735f3eee
					
				
			
		
							
								
								
									
										374
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										374
									
								
								README.md
									
										
									
									
									
								
							|  | @ -30,26 +30,68 @@ supported, I cannot guarantee that _everything_ is supported just yet. | ||||||
| - **Validation** - Syntax checking with precise error locations | - **Validation** - Syntax checking with precise error locations | ||||||
| - **Optimization** - Configurable empty line reduction and whitespace control | - **Optimization** - Configurable empty line reduction and whitespace control | ||||||
| 
 | 
 | ||||||
|  | ### Diagnostics & Analysis | ||||||
|  | 
 | ||||||
|  | - **Comprehensive diagnostics** - Syntax, semantic, style, and best practice | ||||||
|  |   analysis | ||||||
|  | - **Modular analysis** - Run specific diagnostic modules (`lexical`, `syntax`, | ||||||
|  |   `style`, `semantic`) | ||||||
|  | - **LSP-compatible output** - JSON format for editor integration | ||||||
|  | - **Human-readable reports** - Detailed error messages with context and location | ||||||
|  |   information | ||||||
|  | - **Configurable severity** - Control which diagnostic categories to | ||||||
|  |   enable/disable | ||||||
|  | 
 | ||||||
| ## Usage | ## Usage | ||||||
| 
 | 
 | ||||||
|  | ### Formatting | ||||||
|  | 
 | ||||||
| ```bash | ```bash | ||||||
| # Basic formatting | # Format a specific file (in place) | ||||||
| nff -f /etc/nftables.conf | nff format /etc/nftables.conf | ||||||
|  | 
 | ||||||
|  | # Format all .nft files in current directory (in place) | ||||||
|  | nff format | ||||||
| 
 | 
 | ||||||
| # Custom indentation (4 spaces) | # Custom indentation (4 spaces) | ||||||
| nff -f config.nft --indent spaces --spaces 4 | nff format config.nft --indent spaces --spaces 4 | ||||||
| 
 | 
 | ||||||
| # Optimize formatting (reduce empty lines) | # Optimize formatting (reduce empty lines) | ||||||
| nff -f config.nft --optimize | nff format config.nft --optimize | ||||||
| 
 | 
 | ||||||
| # Output to file | # Output to stdout instead of modifying files | ||||||
| nff -f config.nft -o formatted.nft | nff format config.nft --stdout | ||||||
| 
 | 
 | ||||||
| # Syntax validation only | # Syntax validation only | ||||||
| nff -f config.nft --check | nff format config.nft --check | ||||||
| 
 | 
 | ||||||
| # Debug output for development (or debugging) | # Debug output for development (or debugging) | ||||||
| nff -f config.nft --debug | nff format config.nft --debug | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Linting and Diagnostics | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # Run comprehensive diagnostics on a file | ||||||
|  | nff lint /etc/nftables.conf | ||||||
|  | 
 | ||||||
|  | # Lint all .nft files in current directory | ||||||
|  | nff lint | ||||||
|  | 
 | ||||||
|  | # JSON output for editor integration | ||||||
|  | nff lint config.nft --json | ||||||
|  | 
 | ||||||
|  | # Run specific diagnostic modules | ||||||
|  | nff lint config.nft --modules syntax,style | ||||||
|  | 
 | ||||||
|  | # Available modules: lexical, syntax, style, semantic | ||||||
|  | nff lint config.nft --modules semantic | ||||||
|  | 
 | ||||||
|  | # Configure diagnostic settings (note: flags are enabled by default) | ||||||
|  | nff lint config.nft --style-warnings=false --best-practices=false | ||||||
|  | 
 | ||||||
|  | # Debug output with diagnostics | ||||||
|  | nff lint config.nft --debug | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Architecture | ## Architecture | ||||||
|  | @ -69,12 +111,235 @@ graph TD | ||||||
|     AST --> Formatter |     AST --> Formatter | ||||||
|     Formatter --> Output |     Formatter --> Output | ||||||
|     CST --> Formatter |     CST --> Formatter | ||||||
|  | 
 | ||||||
|  |     Input --> Diagnostics[Diagnostic System] | ||||||
|  |     Diagnostics --> LexAnalyzer[Lexical Analyzer] | ||||||
|  |     Diagnostics --> SyntaxAnalyzer[Syntax Analyzer] | ||||||
|  |     Diagnostics --> StyleAnalyzer[Style Analyzer] | ||||||
|  |     Diagnostics --> SemanticAnalyzer[Semantic Analyzer] | ||||||
|  | 
 | ||||||
|  |     LexAnalyzer --> DiagOutput[JSON/Human Output] | ||||||
|  |     SyntaxAnalyzer --> DiagOutput | ||||||
|  |     StyleAnalyzer --> DiagOutput | ||||||
|  |     SemanticAnalyzer --> DiagOutput | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Installation | ## Installation | ||||||
| 
 | 
 | ||||||
| Recommended way of installing nff is to use Nix. | Recommended way of installing nff is to use Nix. | ||||||
| 
 | 
 | ||||||
|  | ### Editor Integration | ||||||
|  | 
 | ||||||
|  | #### Neovim Setup | ||||||
|  | 
 | ||||||
|  | nff can be integrated into Neovim as a diagnostics source for nftables files. | ||||||
|  | Here are several setup approaches: | ||||||
|  | 
 | ||||||
|  | ##### Option 2: Using none-ls | ||||||
|  | 
 | ||||||
|  | ```lua | ||||||
|  | local null_ls = require("null-ls") | ||||||
|  | 
 | ||||||
|  | null_ls.setup({ | ||||||
|  |   sources = { | ||||||
|  |     -- nftables diagnostics | ||||||
|  |     null_ls.builtins.diagnostics.nff.with({ | ||||||
|  |       command = "nff", | ||||||
|  |       args = { "lint", "$FILENAME", "--json" }, | ||||||
|  |       format = "json", | ||||||
|  |       check_exit_code = false, | ||||||
|  |       filetypes = { "nftables" }, | ||||||
|  |     }), | ||||||
|  | 
 | ||||||
|  |     -- nftables formatting | ||||||
|  |     null_ls.builtins.formatting.nff.with({ | ||||||
|  |       command = "nff", | ||||||
|  |       args = { "format", "$FILENAME", "--stdout" }, | ||||||
|  |       filetypes = { "nftables" }, | ||||||
|  |     }), | ||||||
|  |   }, | ||||||
|  | }) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ##### Option 2: Using nvim-lint (recommended) | ||||||
|  | 
 | ||||||
|  | ```lua | ||||||
|  | -- ~/.config/nvim/lua/config/lint.lua | ||||||
|  | require('lint').linters.nff = { | ||||||
|  |   cmd = 'nff', | ||||||
|  |   stdin = false, | ||||||
|  |   args = { 'lint', '%s', '--json' }, | ||||||
|  |   stream = 'stdout', | ||||||
|  |   ignore_exitcode = true, | ||||||
|  |   parser = function(output) | ||||||
|  |     local diagnostics = {} | ||||||
|  |     local ok, decoded = pcall(vim.fn.json_decode, output) | ||||||
|  | 
 | ||||||
|  |     if not ok or not decoded.diagnostics then | ||||||
|  |       return diagnostics | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     for _, diagnostic in ipairs(decoded.diagnostics) do | ||||||
|  |       table.insert(diagnostics, { | ||||||
|  |         lnum = diagnostic.range.start.line, | ||||||
|  |         col = diagnostic.range.start.character, | ||||||
|  |         severity = diagnostic.severity == "Error" and vim.diagnostic.severity.ERROR or vim.diagnostic.severity.WARN, | ||||||
|  |         message = diagnostic.message, | ||||||
|  |         source = "nff", | ||||||
|  |         code = diagnostic.code, | ||||||
|  |       }) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     return diagnostics | ||||||
|  |   end, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | -- Setup linting for nftables files | ||||||
|  | vim.api.nvim_create_autocmd({ "BufEnter", "BufWritePost" }, { | ||||||
|  |   pattern = "*.nft", | ||||||
|  |   callback = function() | ||||||
|  |     require("lint").try_lint("nff") | ||||||
|  |   end, | ||||||
|  | }) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ##### Option 3: Custom Lua Function | ||||||
|  | 
 | ||||||
|  | For a simple custom solution: | ||||||
|  | 
 | ||||||
|  | ```lua | ||||||
|  | -- ~/.config/nvim/lua/nff.lua | ||||||
|  | local M = {} | ||||||
|  | 
 | ||||||
|  | function M.lint_nftables() | ||||||
|  |   local filename = vim.fn.expand('%:p') | ||||||
|  |   if vim.bo.filetype ~= 'nftables' then | ||||||
|  |     return | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   local cmd = { 'nff', 'lint', filename, '--json' } | ||||||
|  | 
 | ||||||
|  |   vim.fn.jobstart(cmd, { | ||||||
|  |     stdout_buffered = true, | ||||||
|  |     on_stdout = function(_, data) | ||||||
|  |       if data then | ||||||
|  |         local output = table.concat(data, '\n') | ||||||
|  |         local ok, result = pcall(vim.fn.json_decode, output) | ||||||
|  | 
 | ||||||
|  |         if ok and result.diagnostics then | ||||||
|  |           local diagnostics = {} | ||||||
|  |           for _, diag in ipairs(result.diagnostics) do | ||||||
|  |             table.insert(diagnostics, { | ||||||
|  |               lnum = diag.range.start.line, | ||||||
|  |               col = diag.range.start.character, | ||||||
|  |               severity = diag.severity == "Error" and vim.diagnostic.severity.ERROR or vim.diagnostic.severity.WARN, | ||||||
|  |               message = diag.message, | ||||||
|  |               source = "nff", | ||||||
|  |             }) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           vim.diagnostic.set(vim.api.nvim_create_namespace('nff'), 0, diagnostics) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end, | ||||||
|  |   }) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | -- Auto-run on save | ||||||
|  | vim.api.nvim_create_autocmd("BufWritePost", { | ||||||
|  |   pattern = "*.nft", | ||||||
|  |   callback = M.lint_nftables, | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | return M | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Diagnostic Categories | ||||||
|  | 
 | ||||||
|  | nff provides comprehensive analysis across multiple categories: | ||||||
|  | 
 | ||||||
|  | ### Syntax Errors | ||||||
|  | 
 | ||||||
|  | - Parse errors with precise location information | ||||||
|  | - Missing tokens (semicolons, braces, etc.) | ||||||
|  | - Unexpected tokens | ||||||
|  | - Unterminated strings | ||||||
|  | - Invalid numbers | ||||||
|  | 
 | ||||||
|  | ### Semantic Validation | ||||||
|  | 
 | ||||||
|  | - Unknown table families (`inet`, `ip`, `ip6`, etc.) | ||||||
|  | - Invalid chain types and hooks | ||||||
|  | - Incorrect priority values | ||||||
|  | - Missing chain policies | ||||||
|  | - Duplicate table/chain names | ||||||
|  | - Invalid CIDR notation | ||||||
|  | - Invalid port ranges | ||||||
|  | 
 | ||||||
|  | ### Style Warnings | ||||||
|  | 
 | ||||||
|  | - Missing shebang line | ||||||
|  | - Inconsistent indentation (mixed tabs/spaces) | ||||||
|  | - Trailing whitespace | ||||||
|  | - Lines exceeding maximum length (configurable) | ||||||
|  | - Excessive empty lines | ||||||
|  | - Preferred syntax alternatives | ||||||
|  | 
 | ||||||
|  | ### Best Practices | ||||||
|  | 
 | ||||||
|  | - Chains without explicit policies | ||||||
|  | - Rules without actions | ||||||
|  | - Overly permissive rules | ||||||
|  | - Duplicate or conflicting rules | ||||||
|  | - Unused variables or sets | ||||||
|  | - Deprecated syntax usage | ||||||
|  | - Missing documentation | ||||||
|  | - Security risks | ||||||
|  | 
 | ||||||
|  | ### Performance Hints | ||||||
|  | 
 | ||||||
|  | - Inefficient rule ordering | ||||||
|  | - Large sets without timeouts | ||||||
|  | - Missing counters where beneficial | ||||||
|  | 
 | ||||||
|  | ## JSON Output Format | ||||||
|  | 
 | ||||||
|  | When using `--json`, nff outputs LSP-compatible diagnostics: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "diagnostics": [ | ||||||
|  |     { | ||||||
|  |       "range": { | ||||||
|  |         "start": { "line": 5, "character": 10 }, | ||||||
|  |         "end": { "line": 5, "character": 20 } | ||||||
|  |       }, | ||||||
|  |       "severity": "Error", | ||||||
|  |       "code": "NFT001", | ||||||
|  |       "source": "nff", | ||||||
|  |       "message": "Expected ';' after policy", | ||||||
|  |       "related_information": [], | ||||||
|  |       "code_actions": [], | ||||||
|  |       "tags": [] | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "file_path": "config.nft", | ||||||
|  |   "source_text": "..." | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Diagnostic Codes | ||||||
|  | 
 | ||||||
|  | nff uses structured diagnostic codes for categorization: | ||||||
|  | 
 | ||||||
|  | - **NFT001-NFT099**: Syntax errors | ||||||
|  | - **NFT101-NFT199**: Semantic errors | ||||||
|  | - **NFT201-NFT299**: Style warnings | ||||||
|  | - **NFT301-NFT399**: Best practice recommendations | ||||||
|  | - **NFT401-NFT499**: Performance hints | ||||||
|  | - **NFT501-NFT599**: Formatting issues | ||||||
|  | - **NFT601-NFT699**: nftables-specific validations | ||||||
|  | 
 | ||||||
| ## Development | ## Development | ||||||
| 
 | 
 | ||||||
| ### Testing | ### Testing | ||||||
|  | @ -196,6 +461,88 @@ table inet protection { | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ## Diagnostics Examples | ||||||
|  | 
 | ||||||
|  | ### Error Detection | ||||||
|  | 
 | ||||||
|  | Input file with issues: | ||||||
|  | 
 | ||||||
|  | ```nftables | ||||||
|  | table inet firewall { | ||||||
|  |   chain input { | ||||||
|  |     type filter hook input priority 100 | ||||||
|  |     tcp dport 22 accept | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Human-readable output: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | Found 2 issues in config.nft: | ||||||
|  | config.nft:3:37: error: Expected ';' after policy [NFT001] | ||||||
|  |    1: table inet firewall { | ||||||
|  |    2:   chain input { | ||||||
|  | →  3:     type filter hook input priority 100 | ||||||
|  |    4:     tcp dport 22 accept | ||||||
|  |    5:   } | ||||||
|  | 
 | ||||||
|  | config.nft:3:1: warning: Filter chain should have an explicit policy [NFT301] | ||||||
|  |    1: table inet firewall { | ||||||
|  |    2:   chain input { | ||||||
|  | →  3:     type filter hook input priority 100 | ||||||
|  |    4:     tcp dport 22 accept | ||||||
|  |    5:   } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | JSON output: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "diagnostics": [ | ||||||
|  |     { | ||||||
|  |       "range": { | ||||||
|  |         "start": { "line": 2, "character": 37 }, | ||||||
|  |         "end": { "line": 2, "character": 37 } | ||||||
|  |       }, | ||||||
|  |       "severity": "Error", | ||||||
|  |       "code": "NFT001", | ||||||
|  |       "source": "nff", | ||||||
|  |       "message": "Expected ';' after policy" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "range": { | ||||||
|  |         "start": { "line": 2, "character": 0 }, | ||||||
|  |         "end": { "line": 2, "character": 37 } | ||||||
|  |       }, | ||||||
|  |       "severity": "Warning", | ||||||
|  |       "code": "NFT301", | ||||||
|  |       "source": "nff", | ||||||
|  |       "message": "Filter chain should have an explicit policy" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "file_path": "config.nft", | ||||||
|  |   "source_text": "..." | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### Style Analysis | ||||||
|  | 
 | ||||||
|  | Input with style issues: | ||||||
|  | 
 | ||||||
|  | ```nftables | ||||||
|  | table inet test{chain input{type filter hook input priority 0;policy drop;tcp dport 22 accept;}} | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Style warnings: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | Found 3 issues in style.nft: | ||||||
|  | style.nft:1:1: warning: Consider adding a shebang line [NFT201] | ||||||
|  | style.nft:1:121: warning: Line too long (98 > 80 characters) [NFT205] | ||||||
|  | style.nft:1:16: warning: Missing space after '{' [NFT503] | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## Contributing | ## Contributing | ||||||
| 
 | 
 | ||||||
| ### Code Style | ### Code Style | ||||||
|  | @ -237,6 +584,17 @@ Below are the design goals of nff's architechture. | ||||||
| - **Memory efficiency**: Streaming token processing where possible | - **Memory efficiency**: Streaming token processing where possible | ||||||
| - **Grammar completeness**: Covers full nftables syntax specification | - **Grammar completeness**: Covers full nftables syntax specification | ||||||
| 
 | 
 | ||||||
|  | ### Diagnostic Architecture | ||||||
|  | 
 | ||||||
|  | The diagnostic system uses a modular architecture with specialized analyzers: | ||||||
|  | 
 | ||||||
|  | - **Modular design**: Each analyzer focuses on specific concerns (lexical, | ||||||
|  |   syntax, style, semantic) | ||||||
|  | - **Configurable analysis**: Enable/disable specific diagnostic categories | ||||||
|  | - **LSP compatibility**: JSON output follows Language Server Protocol standards | ||||||
|  | - **Performance optimized**: Concurrent analysis when possible | ||||||
|  | - **Extensible**: Easy to add new diagnostic rules and categories | ||||||
|  | 
 | ||||||
| ## License | ## License | ||||||
| 
 | 
 | ||||||
| nff is licensed under [MPL v2.0](LICENSE). See license file for more details on | nff is licensed under [MPL v2.0](LICENSE). See license file for more details on | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue