Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] implement go to policy #13

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions packages/language-server/go-to-definitions/go-to-policy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const lsp = require('vscode-languageserver/node')
const path = require('path')
const fs = require('fs').promises

module.exports = async function goToPolicy(document, position) {
const fileName = path.basename(document.uri)

if (fileName !== 'policies.js') {
return null
}

const policyInfo = extractPolicyInfo(document, position)

if (!policyInfo) {
return null
}

const projectRoot = path.dirname(path.dirname(document.uri))
const fullPolicyPath = resolvePolicyPath(projectRoot, policyInfo.policy)

if (await fileExists(fullPolicyPath)) {
return lsp.Location.create(fullPolicyPath, lsp.Range.create(0, 0, 0, 0))
}

return null
}

function extractPolicyInfo(document, position) {
const text = document.getText()
const offset = document.offsetAt(position)

// This regex matches policy definitions, including arrays of policies and boolean values
const regex =
/(['"])((?:\*|[\w-]+(?:\/\*?)?))?\1\s*:\s*((?:\[?\s*(?:(?:['"][\w-]+['"](?:\s*,\s*)?)+)\s*\]?)|true|false)/g
let match

while ((match = regex.exec(text)) !== null) {
const [fullMatch, , route, policiesOrBoolean] = match
const start = match.index
const end = start + fullMatch.length

// Check if the cursor is anywhere within the entire match
if (start <= offset && offset <= end) {
// If policiesOrBoolean is a boolean, ignore it
if (policiesOrBoolean === true || policiesOrBoolean === false) {
continue
}

// Remove brackets if present and split into individual policies
const policies = policiesOrBoolean
.replace(/^\[|\]$/g, '')
.split(',')
.map((p) => p.trim().replace(/^['"]|['"]$/g, ''))

// Find which policy the cursor is on
let currentStart = start + fullMatch.indexOf(policiesOrBoolean)
for (const policy of policies) {
const policyStart = text.indexOf(policy, currentStart)
const policyEnd = policyStart + policy.length

if (offset >= policyStart && offset <= policyEnd) {
return {
policy,
range: lsp.Range.create(
document.positionAt(policyStart),
document.positionAt(policyEnd)
)
}
}

currentStart = policyEnd
}
}
}

return null
}

function resolvePolicyPath(projectRoot, policyPath) {
return path.join(projectRoot, 'api', 'policies', `${policyPath}.js`)
}

async function fileExists(filePath) {
try {
await fs.access(new URL(filePath))
return true
} catch {
return false
}
}
12 changes: 8 additions & 4 deletions packages/language-server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const lsp = require('vscode-languageserver/node')
const TextDocument = require('vscode-languageserver-textdocument').TextDocument
const validateDocument = require('./validators/validate-document')
const goToAction = require('./go-to-definitions/go-to-action')
const goToPolicy = require('./go-to-definitions/go-to-policy')

const connection = lsp.createConnection(lsp.ProposedFeatures.all)
const documents = new lsp.TextDocuments(TextDocument)
Expand Down Expand Up @@ -32,10 +33,13 @@ connection.onDefinition(async (params) => {
if (!document) {
return null
}
const definitions = []
const actionDefinitions = await goToAction(document, params.position)
definitions.push(actionDefinitions)
return definitions || null

const actionDefinition = await goToAction(document, params.position)
const policyDefinition = await goToPolicy(document, params.position)

const definitions = [actionDefinition, policyDefinition].filter(Boolean)

return definitions.length > 0 ? definitions : null
})

documents.listen(connection)
Expand Down
Loading