Skip to content
Open
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
27 changes: 18 additions & 9 deletions src/ai/CfnAI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ export class CfnAI implements SettingsConfigurable, Closeable {
throw new Error(`Template not found ${toString(templateFile)}`);
}

return await agent.execute(
await Prompts.describeTemplate(document.contents()),
await this.getToolsWithFallback(),
);
const content = document.contents();
if (!content) {
throw new Error('Document content is undefined');
}
return await agent.execute(await Prompts.describeTemplate(content), await this.getToolsWithFallback());
});
}

Expand All @@ -72,10 +73,11 @@ export class CfnAI implements SettingsConfigurable, Closeable {
throw new Error(`Template not found ${toString(templateFile)}`);
}

return await agent.execute(
await Prompts.optimizeTemplate(document.contents()),
await this.getToolsWithFallback(),
);
const content = document.contents();
if (!content) {
throw new Error('Document content is undefined');
}
return await agent.execute(await Prompts.optimizeTemplate(content), await this.getToolsWithFallback());
});
}

Expand All @@ -86,8 +88,12 @@ export class CfnAI implements SettingsConfigurable, Closeable {
return;
}

const content = document.contents();
if (!content) {
throw new Error('Document content is undefined');
}
return await agent.execute(
await Prompts.analyzeDiagnostic(document.contents(), diagnostics),
await Prompts.analyzeDiagnostic(content, diagnostics),
await this.getToolsWithFallback(),
);
});
Expand All @@ -107,6 +113,9 @@ export class CfnAI implements SettingsConfigurable, Closeable {
}

const templateContent = document.contents();
if (!templateContent) {
throw new Error('Document content is undefined');
}

const resourceTypes = this.relationshipSchemaService.extractResourceTypesFromTemplate(templateContent);
const relationshipContext = this.relationshipSchemaService.getRelationshipContext(resourceTypes);
Expand Down
3 changes: 3 additions & 0 deletions src/codeLens/ManagedResourceCodeLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export class ManagedResourceCodeLens {
}

const text = document.getText();
if (!text) {
return [];
}
const lines = text.split('\n');

for (const [, resourceContext] of resourcesMap) {
Expand Down
3 changes: 3 additions & 0 deletions src/codeLens/StackActionsCodeLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export function getStackActionsCodeLenses(

let codeLensLine = 0;
const lines = document.getLines();
if (!lines) {
return [];
}
for (const [i, line] of lines.entries()) {
const lineContents = line.trim();
if (lineContents.length > 0 && !lineContents.startsWith('#')) {
Expand Down
7 changes: 6 additions & 1 deletion src/context/FileContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@ export class FileContextManager {
return undefined;
}

const content = document.contents();
if (!content) {
return undefined;
}

try {
return new FileContext(uri, document.documentType, document.contents());
return new FileContext(uri, document.documentType, content);
} catch (error) {
this.log.error(error, `Failed to create file context ${uri}`);
return undefined;
Expand Down
57 changes: 37 additions & 20 deletions src/document/Document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ export class Document {
private cachedParsedContent: unknown;

constructor(
private readonly textDocument: TextDocument,
public readonly uri: DocumentUri,
private readonly textDocument: (uri: string) => TextDocument | undefined,
detectIndentation: boolean = true,
fallbackTabSize: number = DefaultSettings.editor.tabSize,
public readonly uri: DocumentUri = textDocument.uri,
public readonly languageId: string = textDocument.languageId,
public readonly version: number = textDocument.version,
public readonly lineCount: number = textDocument.lineCount,
) {
const { extension, type } = detectDocumentType(textDocument.uri, textDocument.getText());
const doc = this.getTextDocument();
const { extension, type } = doc
? detectDocumentType(doc.uri, doc.getText())
: { extension: '', type: DocumentType.YAML };

this.extension = extension;
this.documentType = type;
Expand All @@ -36,12 +36,28 @@ export class Document {
this.processIndentation(detectIndentation, fallbackTabSize);
}

private getTextDocument(): TextDocument | undefined {
return this.textDocument(this.uri);
}

public get languageId(): string | undefined {
return this.getTextDocument()?.languageId;
}

public get version(): number | undefined {
return this.getTextDocument()?.version;
}

public get lineCount(): number | undefined {
return this.getTextDocument()?.lineCount;
}

public get cfnFileType(): CloudFormationFileType {
return this._cfnFileType;
}

public updateCfnFileType(): void {
const content = this.textDocument.getText();
const content = this.getTextDocument()?.getText() ?? '';
if (!content.trim()) {
this._cfnFileType = CloudFormationFileType.Empty;
this.cachedParsedContent = undefined;
Expand All @@ -54,14 +70,12 @@ export class Document {
} catch {
// If parsing fails, leave cfnFileType unchanged and clear cache
this.cachedParsedContent = undefined;
this.log.debug(
`Failed to parse document ${this.textDocument.uri}, keeping cfnFileType as ${this._cfnFileType}`,
);
this.log.debug(`Failed to parse document ${this.uri}, keeping cfnFileType as ${this._cfnFileType}`);
}
}

private parseContent(): unknown {
const content = this.textDocument.getText();
const content = this.getTextDocument()?.getText() ?? '';
if (this.documentType === DocumentType.JSON) {
return JSON.parse(content);
}
Expand Down Expand Up @@ -124,27 +138,27 @@ export class Document {
}

public getText(range?: Range) {
return this.textDocument.getText(range);
return this.getTextDocument()?.getText(range);
}

public getLines(): string[] {
return this.getText().split('\n');
public getLines(): string[] | undefined {
return this.getText()?.split('\n');
}

public positionAt(offset: number) {
return this.textDocument.positionAt(offset);
return this.getTextDocument()?.positionAt(offset);
}

public offsetAt(position: Position) {
return this.textDocument.offsetAt(position);
return this.getTextDocument()?.offsetAt(position);
}

public isTemplate() {
return this.cfnFileType === CloudFormationFileType.Template;
}

public contents() {
return this.textDocument.getText();
return this.getTextDocument()?.getText();
}

public metadata(): DocumentMetadata {
Expand All @@ -154,9 +168,9 @@ export class Document {
ext: this.extension,
type: this.documentType,
cfnType: this.cfnFileType,
languageId: this.languageId,
version: this.version,
lineCount: this.lineCount,
languageId: this.languageId ?? '',
version: this.version ?? 0,
lineCount: this.lineCount ?? 0,
};
}

Expand All @@ -176,6 +190,9 @@ export class Document {

private detectIndentationFromContent(): number | undefined {
const content = this.contents();
if (!content) {
return undefined;
}
const lines = content.split('\n');

const maxLinesToAnalyze = Math.min(lines.length, 30);
Expand Down
29 changes: 21 additions & 8 deletions src/document/DocumentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,22 @@ export class DocumentManager implements SettingsConfigurable, Closeable {
}

get(uri: string) {
let document = this.documentMap.get(uri);
if (document) {
return document;
}

const textDocument = this.documents.get(uri);
if (!textDocument) {
return;
}

document = new Document(textDocument, this.editorSettings.detectIndentation, this.editorSettings.tabSize);
let document = this.documentMap.get(uri);
if (document) {
return document;
}

document = new Document(
uri,
(u) => this.documents.get(u),
this.editorSettings.detectIndentation,
this.editorSettings.tabSize,
);
this.documentMap.set(uri, document);
return document;
}
Expand All @@ -73,7 +78,12 @@ export class DocumentManager implements SettingsConfigurable, Closeable {
for (const textDoc of this.documents.all()) {
let document = this.documentMap.get(textDoc.uri);
if (!document) {
document = new Document(textDoc, this.editorSettings.detectIndentation, this.editorSettings.tabSize);
document = new Document(
textDoc.uri,
(u) => this.documents.get(u),
this.editorSettings.detectIndentation,
this.editorSettings.tabSize,
);
this.documentMap.set(textDoc.uri, document);
}
allDocs.push(document);
Expand Down Expand Up @@ -166,7 +176,10 @@ export class DocumentManager implements SettingsConfigurable, Closeable {
private emitDocSizeMetrics() {
for (const doc of this.documentMap.values()) {
if (doc.isTemplate()) {
this.telemetry.histogram('documents.template.size.bytes', byteSize(doc.contents()), { unit: 'By' });
const content = doc.contents();
if (content) {
this.telemetry.histogram('documents.template.size.bytes', byteSize(content), { unit: 'By' });
}
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/handlers/DocumentHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export function didOpenHandler(components: ServerComponents): (event: TextDocume
}

const content = document.contents();
if (!content) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sill be true if contents is both undefined or empty which breaks line 33

log.error(`No content found for document ${uri}`);
return;
}

if (document.isTemplate() || document.cfnFileType === CloudFormationFileType.Empty) {
try {
Expand Down Expand Up @@ -55,8 +59,12 @@ export function didChangeHandler(
}

// This is the document AFTER changes
const document = new Document(textDocument);
const document = new Document(textDocument.uri, () => textDocument);
const finalContent = document.getText();
if (!finalContent) {
log.error(`No content found for document ${documentUri}`);
return;
}

const tree = components.syntaxTreeManager.getSyntaxTree(documentUri);

Expand Down
2 changes: 1 addition & 1 deletion src/handlers/StackHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export function getTemplateArtifactsHandler(
components.s3Service,
document.documentType,
document.uri,
document.contents(),
document.contents() ?? '',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be checked at like 92

);
const artifacts = template.getTemplateArtifacts();
return { artifacts };
Expand Down
23 changes: 18 additions & 5 deletions src/resourceState/ResourceStateImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export class ResourceStateImporter {
if (insertPosition.replaceEntireFile) {
// Replace entire file with properly formatted JSON
snippetText = docFormattedText;
const endPosition = { line: document.lineCount, character: 0 };
const endPosition = { line: document.lineCount ?? 0, character: 0 };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we exit early if theres no document?

textEdit = TextEdit.replace(Range.create({ line: 0, character: 0 }, endPosition), snippetText);
} else {
// Insert at specific position
Expand Down Expand Up @@ -420,7 +420,7 @@ export class ResourceStateImporter {
: { line: resourcesSection.endPosition.row + 1, character: 0 };
} else {
// Find the last non-empty line
let lastNonEmptyLine = document.lineCount - 1;
let lastNonEmptyLine = document.lineCount ? document.lineCount - 1 : 0;
while (lastNonEmptyLine >= 0 && document.getLine(lastNonEmptyLine)?.trim().length === 0) {
lastNonEmptyLine--;
}
Expand All @@ -434,12 +434,25 @@ export class ResourceStateImporter {
};
}

let line = resourcesSection ? resourcesSection.endPosition.row : document.lineCount - 1;
let line = resourcesSection
? resourcesSection.endPosition.row
: document.lineCount
? document.lineCount - 1
: 0;

// For JSON without Resources section, check if file is essentially empty
if (!resourcesSection) {
try {
const parsed = JSON.parse(document.getText()) as Record<string, unknown>;
const text = document.getText();
if (!text) {
return {
position: { line: 0, character: 0 },
commaPrefixNeeded: false,
newLineSuffixNeeded: false,
replaceEntireFile: true,
};
}
const parsed = JSON.parse(text) as Record<string, unknown>;
const hasContent = Object.keys(parsed).length > 0;

// If no content, replace entire file
Expand Down Expand Up @@ -495,7 +508,7 @@ export class ResourceStateImporter {
}
// malformed case, allow import to end of document
return {
position: { line: document.lineCount, character: 0 },
position: { line: document.lineCount ?? 0, character: 0 },
commaPrefixNeeded: false,
newLineSuffixNeeded: false,
replaceEntireFile: false,
Expand Down
10 changes: 9 additions & 1 deletion src/stacks/actions/StackActionOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,22 @@ export async function processChangeSet(
throw new ResponseError(ErrorCodes.InvalidParams, `Document not found: ${params.uri}`);
}
let templateBody = document.contents();
if (!templateBody) {
throw new ResponseError(ErrorCodes.InvalidParams, `Document content is undefined: ${params.uri}`);
}
let templateS3Url: string | undefined;
let expectedETag: string | undefined;
try {
if (params.s3Bucket) {
const s3KeyPrefix = params.s3Key?.includes('/')
? params.s3Key.slice(0, params.s3Key.lastIndexOf('/'))
: undefined;
const template = new ArtifactExporter(s3Service, document.documentType, document.uri, document.contents());
const template = new ArtifactExporter(
s3Service,
document.documentType,
document.uri,
document.contents() ?? '',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably throw at line 100

);

const exportedTemplate = await template.export(params.s3Bucket, s3KeyPrefix);

Expand Down
Loading
Loading