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 === undefined) {
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 === undefined) {
log.error(`No content found for document ${documentUri}`);
return;
}

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

Expand Down
12 changes: 6 additions & 6 deletions src/handlers/StackHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ export function getTemplateArtifactsHandler(
throw new Error(`Cannot retrieve file with uri: ${params}`);
}

const template = new ArtifactExporter(
components.s3Service,
document.documentType,
document.uri,
document.contents(),
);
const content = document.contents();
if (content === undefined) {
throw new Error(`Cannot retrieve content for file: ${params}`);
}

const template = new ArtifactExporter(components.s3Service, document.documentType, document.uri, content);
const artifacts = template.getTemplateArtifacts();
return { artifacts };
} catch (error) {
Expand Down
24 changes: 20 additions & 4 deletions src/resourceState/ResourceStateImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ export class ResourceStateImporter {
if (insertPosition.replaceEntireFile) {
// Replace entire file with properly formatted JSON
snippetText = docFormattedText;
if (document.lineCount === undefined) {
return this.getFailureResponse('Import failed. Document is no longer available.');
}
const endPosition = { line: document.lineCount, character: 0 };
textEdit = TextEdit.replace(Range.create({ line: 0, character: 0 }, endPosition), snippetText);
} else {
Expand Down Expand Up @@ -420,7 +423,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 +437,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,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure what the expected default should be here

};
}
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 +511,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
5 changes: 4 additions & 1 deletion src/stacks/actions/StackActionOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,17 @@ 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, templateBody);

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

Expand Down
Loading
Loading