Skip to content

Commit

Permalink
feat: add native code lens support (#207)
Browse files Browse the repository at this point in the history
* feat: add native code lens support

This PR add introduces native support for Infracost code lenses. Meaning we can drop our reliance on
the Terraform extension for code symbols. Instead we now use the Infracost metadata to determine the
position and range of the code lens. This should speed up the extension for most users, and improve the
robustness of code lens support.

* fix: bump version

* feat: enable graph evaluator
  • Loading branch information
hugorut authored Mar 6, 2024
1 parent a8c0900 commit 6395c96
Show file tree
Hide file tree
Showing 8 changed files with 28 additions and 82 deletions.
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ See a tree overview of your Infrastructure costs. See which projects, files and

Open VS Code and install the [Infracost extension](https://marketplace.visualstudio.com/items?itemName=Infracost.infracost).

This will also install the the [Hashicorp Terraform extension](https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform) extension if you don't already have it.
![](https://github.com/infracost/vscode-infracost/blob/master/.github/assets/infracost-install.png?raw=true)

### 2. Connect VS Code to Infracost

Once you've installed the extension, you'll need to connect to your editor to your Infracost account. Click the "connect to Infracost" button in the Infracost sidebar.
Expand Down Expand Up @@ -179,8 +176,7 @@ We love any contribution, big or small. If you want to change the Infracost VS C
1. Clone the repo.
2. `yarn` install all the dependencies.
3. Open the repo in VS Code.
4. Install the [Terraform VS Code extension](https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform) in VS Code.
5. Inside the editor, press F5. VS Code will compile and run the extension in a new Development Host window.
6. Open a Terraform project, and navigate to a valid file. If all the previous steps have been followed correctly, you should see Infracost cost estimates above supported resource blocks.
4. Inside the editor, press F5. VS Code will compile and run the extension in a new Development Host window.
5. Open a Terraform project, and navigate to a valid file. If all the previous steps have been followed correctly, you should see Infracost cost estimates above supported resource blocks.

Once you're finished with your work, open a PR, and we'll be happy to review it as soon as possible.
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "infracost",
"displayName": "Infracost",
"description": "Cloud cost estimates for Terraform in your editor",
"version": "0.2.22",
"version": "0.2.23",
"publisher": "Infracost",
"license": "Apache-2.0",
"icon": "infracost-logo.png",
Expand All @@ -19,9 +19,7 @@
"terraform",
"hcl"
],
"extensionDependencies": [
"HashiCorp.terraform"
],
"extensionDependencies": [],
"repository": {
"type": "git",
"url": "https://github.com/infracost/vscode-infracost.git"
Expand Down
8 changes: 7 additions & 1 deletion src/block.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ViewColumn, WebviewPanel, window } from 'vscode';
import { Position, Range, ViewColumn, WebviewPanel, window } from 'vscode';
import { TemplateDelegate } from 'handlebars';
import { infracostJSON } from './cli';
import webviews from './webview';
Expand All @@ -8,8 +8,11 @@ export default class Block {

webview: WebviewPanel | undefined;

lensPosition: Range;

constructor(
public name: string,
public startLine: number,
public filename: string,
public currency: string,
public template: TemplateDelegate
Expand All @@ -21,6 +24,9 @@ export default class Block {
this.webview = undefined;
});
}

const position = new Position(this.startLine - 1, 0);
this.lensPosition = new Range(position, position);
}

key(): string {
Expand Down
3 changes: 3 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ export namespace infracostJSON {

export interface ResourceMetadata {
filename: string;
startLine: number;
calls: Call[];
}

export interface Call {
blockName: string;
filename: string;
startLine: number;
}

export interface CostComponent {
Expand Down Expand Up @@ -105,6 +107,7 @@ export default class CLI {
INFRACOST_CLI_PLATFORM: 'vscode',
INFRACOST_NO_COLOR: 'true',
INFRACOST_SKIP_UPDATE_CHECK: 'true',
INFRACOST_GRAPH_EVALUATOR: 'true'
},
});

Expand Down
4 changes: 2 additions & 2 deletions src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export default class File {
return formatter.format(cost);
}

setBlock(name: string): Block {
setBlock(name: string, startLine: number): Block {
if (this.blocks[name] === undefined) {
this.blocks[name] = new Block(name, this.name, this.currency, this.template);
this.blocks[name] = new Block(name, startLine, this.name, this.currency, this.template);
}

return this.blocks[name];
Expand Down
75 changes: 9 additions & 66 deletions src/lens.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import {
CodeLens,
CodeLensProvider,
Event,
TextDocument,
CodeLens,
commands,
SymbolInformation,
Position,
DocumentSymbol,
} from 'vscode';
import Workspace from './workspace';
import { cleanFilename } from './utils';
Expand Down Expand Up @@ -34,75 +30,22 @@ export default class InfracostLensProvider implements CodeLensProvider {
logger.debug(`providing codelens for file ${filename}`);

const blocks = this.workspace.project(filename);

const symbols = await commands.executeCommand<SymbolInformation[]>(
'vscode.executeDocumentSymbolProvider',
document.uri
);
if (symbols === undefined) {
logger.debug(`no valid symbols found for file ${filename}`);
return lenses;
}

for (const sym of symbols) {
logger.debug(`evaluating symbol: ${sym.name}`);

if (sym.name.indexOf('resource') === -1 && sym.name.indexOf('module') === -1) {
logger.debug(`skipping symbol as not supported for Infracost costs`);
for (const block of Object.values(blocks)) {
if (block.filename !== filename) {
continue;
}

const line = document.lineAt(getRangeFromSymbol(sym).start);
const resourceKey = sym.name
.replace(/\s+/g, '.')
.replace(/"/g, '')
.replace(/^resource\./g, '');

logger.debug(`finding symbol cost using key: ${resourceKey}`);

if (blocks[resourceKey] !== undefined) {
const block = blocks[resourceKey];
const cost = block.cost();
logger.debug(`found Infracost price for symbol: ${resourceKey} cost: ${cost}`);

let msg = `Total monthly cost: ${cost}`;
if (this.workspace.loading) {
msg = 'loading...';
}
const cost = block.cost();

const cmd = new InfracostCommand(msg, block);
lenses.push(new CodeLens(line.range.with(new Position(line.range.start.line, 0)), cmd));
continue;
let msg = `Total monthly cost: ${cost}`;
if (this.workspace.loading) {
msg = 'loading...';
}

logger.debug(`no registered blocks matching key: ${resourceKey}`);
const cmd = new InfracostCommand(msg, block);
lenses.push(new CodeLens(block.lensPosition, cmd));
}

return lenses;
}
}

function getRangeFromSymbol(symbol: DocumentSymbol | SymbolInformation) {
return isDocumentSymbol(symbol) ? symbol.range : symbol.location.range;
}

function isDocumentSymbol(symbol: DocumentSymbol | SymbolInformation): symbol is DocumentSymbol {
return is<DocumentSymbol>(symbol, 'children');
}

/* eslint-disable @typescript-eslint/no-explicit-any */
function is<T extends object>(o: T | null | undefined): o is T;
function is<T extends object>(o: object, prop: keyof T, value?: any): o is T;
function is<T extends object>(o: object, matcher: (o: object) => boolean): o is T;
function is<T extends object>(
o: object,
propOrMatcher?: keyof T | ((o: any) => boolean),
value?: any
): o is T {
if (propOrMatcher == null) return o != null;
if (typeof propOrMatcher === 'function') return propOrMatcher(o);

return value === undefined
? (o as any)[propOrMatcher] !== undefined
: (o as any)[propOrMatcher] === value;
}
4 changes: 2 additions & 2 deletions src/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ export default class Project {
public template: TemplateDelegate
) {}

setBlock(filename: string, name: string): Block {
setBlock(filename: string, name: string, startLine: number): Block {
if (this.files[filename] === undefined) {
this.files[filename] = new File(filename, this.currency, this.template);
}

const file = this.files[filename];
const block = file.setBlock(name);
const block = file.setBlock(name, startLine);

if (this.blocks[name] === undefined) {
this.blocks[name] = block;
Expand Down
2 changes: 1 addition & 1 deletion src/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ export default class Workspace {
const filename = cleanFilename(call.filename);
logger.debug(`adding file: ${filename} to project: ${projectPath}`);

formatted.setBlock(filename, call.blockName).resources.push(resource);
formatted.setBlock(filename, call.blockName, call.startLine).resources.push(resource);
this.addProjectToFile(filename, projectPath);
}
}
Expand Down

0 comments on commit 6395c96

Please sign in to comment.