diff --git a/.gitmodules b/.gitmodules index f8978d3025..50b8ddc89f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1073,6 +1073,9 @@ [submodule "vendor/grammars/portugol-grammar"] path = vendor/grammars/portugol-grammar url = https://github.com/luisgbr1el/portugol-grammar.git +[submodule "vendor/grammars/powerquery-language"] + path = vendor/grammars/powerquery-language + url = https://github.com/microsoft/powerquery-language.git [submodule "vendor/grammars/powershell"] path = vendor/grammars/powershell url = https://github.com/PowerShell/EditorSyntax diff --git a/grammars.yml b/grammars.yml index a7db2d7ed1..43b0633a7c 100644 --- a/grammars.yml +++ b/grammars.yml @@ -988,6 +988,8 @@ vendor/grammars/polar-grammar: - source.polar vendor/grammars/portugol-grammar: - source.portugol +vendor/grammars/powerquery-language: +- source.powerquery vendor/grammars/powershell: - source.powershell vendor/grammars/praatvscode: diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index a1e2ed2f67..ec80ce26c1 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -5937,6 +5937,16 @@ PostScript: - postscr ace_mode: text language_id: 291 +Power Query: + type: programming + aliases: + - powerquery + extensions: + - ".pq" + tm_scope: source.powerquery + ace_mode: text + color: "#d38e0d" + language_id: 37496382 PowerBuilder: type: programming color: "#8f0f8d" @@ -7674,7 +7684,7 @@ TMDL: extensions: - ".tmdl" aliases: - - "Tabular Model Definition Language" + - Tabular Model Definition Language tm_scope: source.tmdl ace_mode: text language_id: 769162295 diff --git a/samples/PowerQuery/fnMxToolboxLookup.pq b/samples/PowerQuery/fnMxToolboxLookup.pq new file mode 100644 index 0000000000..bbd1e333af --- /dev/null +++ b/samples/PowerQuery/fnMxToolboxLookup.pq @@ -0,0 +1,134 @@ +/* +============================================================================== + Function Name : fnMxToolboxLookup + SOAR Stage : SOAR 1 – API Lookup (Inner) + Returns : table (canonical schema; 1 row) + Purpose : Call MXToolbox API and return a single-row table normalized + to the canonical schema contract WITHOUT type/value leakage. + + Dependencies : + - pMxApiKey + - q_MxSchema_Contract + + Guardrails : + - MUST return a TABLE value (never a naked type) + - MUST NOT forward-reference values + - MUST enforce schema using a LIST of {ColumnName, Type} pairs +============================================================================== +*/ +(domain as text, command as text) => +let + BaseUrl = "https://api.mxtoolbox.com/api/v1/lookup/" & command, + Url = BaseUrl & "?" & Uri.BuildQueryString([ argument = domain ]), + Headers = [ + Accept = "application/json", + Authorization = pMxApiKey + ], + + Response = + Web.Contents( + Url, + [ Headers = Headers ] + ), + + Json = Json.Document(Response), + + UIDVal = try Json[UID] otherwise null, + CommandArgumentVal = try Json[CommandArgument] otherwise domain, + CommandValue = try Json[Command] otherwise command, + MxRepValue = try Json[MxRep] otherwise null, + DnsProviderVal = try Json[DnsServiceProvider] otherwise null, + TimeRecordedRaw = try Json[TimeRecorded] otherwise null, + ReportingNSVal = try Json[ReportingNameServer] otherwise null, + TimeToCompleteVal = try Json[TimeToComplete] otherwise null, + IsEndpointVal = try Json[IsEndpoint] otherwise null, + HasSubscriptionsVal = try Json[HasSubscriptions] otherwise null, + + TimeRecordedVal = + try DateTimeZone.From(TimeRecordedRaw) + otherwise try DateTime.From(TimeRecordedRaw) + otherwise null, + + FailedRaw = try Json[Failed] otherwise null, + WarningsRaw = try Json[Warnings] otherwise null, + PassedRaw = try Json[Passed] otherwise null, + TimeoutsRaw = try Json[Timeouts] otherwise null, + + FailedList = if FailedRaw is list then FailedRaw else {}, + WarningsList = if WarningsRaw is list then WarningsRaw else {}, + PassedList = if PassedRaw is list then PassedRaw else {}, + TimeoutsList = if TimeoutsRaw is list then TimeoutsRaw else {}, + + FailedCount = List.Count(FailedList), + WarningsCount = List.Count(WarningsList), + PassedCount = List.Count(PassedList), + TimeoutsCount = List.Count(TimeoutsList), + + HasDmarcRecord = + if Text.Lower(command) = "dmarc" then FailedCount = 0 else null, + + OutputRecord = [ + UID = UIDVal, + Domain = CommandArgumentVal, + Command = CommandValue, + CommandArgument = CommandArgumentVal, + MxRep = MxRepValue, + DnsProvider = DnsProviderVal, + ReportingNameServer = ReportingNSVal, + TimeRecorded = TimeRecordedVal, + TimeToComplete = TimeToCompleteVal, + IsEndpoint = IsEndpointVal, + HasSubscriptions = HasSubscriptionsVal, + FailedCount = FailedCount, + WarningsCount = WarningsCount, + PassedCount = PassedCount, + TimeoutsCount = TimeoutsCount, + FailedList = FailedList, + WarningsList = WarningsList, + PassedList = PassedList, + TimeoutsList = TimeoutsList, + HasDmarcRecord = HasDmarcRecord + ], + + OutputTable = + #table( + Record.FieldNames(OutputRecord), + { Record.FieldValues(OutputRecord) } + ), + + Renamed = + Table.RenameColumns( + OutputTable, + {{"Command", "MxCommand"}}, + MissingField.Ignore + ), + + /* Build a valid TransformColumnTypes mapping from q_MxSchema_Contract */ + SchemaFieldRecord = + Type.RecordFields( + Type.TableRow( + Value.Type(q_MxSchema_Contract) + ) + ), + + FullTypeMap = + List.Transform( + Record.FieldNames(SchemaFieldRecord), + (colName) => { colName, Record.Field(SchemaFieldRecord, colName)[Type] } + ), + + /* Only apply types for columns that actually exist in this table */ + PresentCols = Table.ColumnNames(Renamed), + TypeMapFiltered = + List.Select( + FullTypeMap, + (pair) => List.Contains(PresentCols, pair{0}) + ), + + Normalized = + Table.TransformColumnTypes( + Renamed, + TypeMapFiltered + ) +in + Normalized diff --git a/samples/PowerQuery/q_MxToolboxScore_SOAR3.pq b/samples/PowerQuery/q_MxToolboxScore_SOAR3.pq new file mode 100644 index 0000000000..ef8d053c52 --- /dev/null +++ b/samples/PowerQuery/q_MxToolboxScore_SOAR3.pq @@ -0,0 +1,52 @@ +/* +============================================================================== + Query Name : q_MxToolboxScore_SOAR3 + SOAR Stage : SOAR 3 – Deterministic Risk Scoring + Load To : Connection Only + Expansion : NONE (Scalar only) + Purpose : Compute a deterministic risk score and tier classification + from SOAR‑3 scalar MXToolbox indicators. + Guardrails : + - MUST be explainable and auditable + - MUST NOT infer maliciousness beyond documented tiers +============================================================================== +*/ +let + Source = q_MxToolboxOutput_SOAR3, + + Scored = + Table.AddColumn( + Source, + "RiskScore", + each + // Failures + (if [FailedCount] = 0 then 0 + else if [FailedCount] <= 2 then 30 + else 60) + + // Warnings + + (if [WarningsCount] = 0 then 0 + else if [WarningsCount] <= 3 then 10 + else 20) + + // Timeouts + + (if [TimeoutsCount] > 0 then 10 else 0) + + // DMARC + + (if [HasDmarcRecord] = true then 0 else 20), + Int64.Type + ), + + Tiered = + Table.AddColumn( + Scored, + "ReputationTier", + each + if [RiskScore] < 10 then "T1_Clean" + else if [RiskScore] < 30 then "T2_Degraded" + else if [RiskScore] < 60 then "T3_HighRisk" + else "T4_Malicious", + type text + ) +in + Tiered diff --git a/vendor/README.md b/vendor/README.md index cfa3e60857..a3b97619b2 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -488,6 +488,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Portugol:** [luisgbr1el/portugol-grammar](https://github.com/luisgbr1el/portugol-grammar) - **PostCSS:** [hudochenkov/Syntax-highlighting-for-PostCSS](https://github.com/hudochenkov/Syntax-highlighting-for-PostCSS) - **PostScript:** [Alhadis/Atom-PostScript](https://github.com/Alhadis/Atom-PostScript) +- **Power Query:** [microsoft/powerquery-language](https://github.com/microsoft/powerquery-language) - **PowerBuilder:** [informaticon/PowerBuilder.tmbundle](https://github.com/informaticon/PowerBuilder.tmbundle) - **PowerShell:** [PowerShell/EditorSyntax](https://github.com/PowerShell/EditorSyntax) - **Praat:** [orhunulusahin/praatvscode](https://github.com/orhunulusahin/praatvscode) diff --git a/vendor/grammars/powerquery-language b/vendor/grammars/powerquery-language new file mode 160000 index 0000000000..b66dc932b0 --- /dev/null +++ b/vendor/grammars/powerquery-language @@ -0,0 +1 @@ +Subproject commit b66dc932b03fd5cf03f3b20a9e83fac97d897e65 diff --git a/vendor/licenses/git_submodule/powerquery-language.dep.yml b/vendor/licenses/git_submodule/powerquery-language.dep.yml new file mode 100644 index 0000000000..c304d96024 --- /dev/null +++ b/vendor/licenses/git_submodule/powerquery-language.dep.yml @@ -0,0 +1,31 @@ +--- +name: powerquery-language +version: b66dc932b03fd5cf03f3b20a9e83fac97d897e65 +type: git_submodule +homepage: https://github.com/microsoft/powerquery-language.git +license: mit +licenses: +- sources: LICENSE + text: |2 + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE +notices: []