Skip to content
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
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,15 @@
"default": 80,
"description": "Maximum line length for DeviceTree files. Lines longer than this will be wrapped."
},
"devicetree.enableWarnings": {
"devicetree.diagnostics.enableWarnings": {
"type": "boolean",
"default": true,
"description": "Enable warnings for DeviceTree files. If disabled, only errors will be reported."
},
"devicetree.diagnostics.lineLengthIncludeComments": {
"type": "boolean",
"default": true,
"markdownDescription": "Include comments when calculating line length for warnings."
}
}
}
Expand Down
66 changes: 64 additions & 2 deletions src/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,29 @@ import * as vscode from 'vscode';
export class DtsDiagnosticsProvider {
private diagnosticCollection: vscode.DiagnosticCollection;
private maxLineLength: number;
private includeComments: boolean;
private tabSize: number;

constructor(maxLineLength: number) {
constructor(maxLineLength: number, includeComments: boolean) {
this.diagnosticCollection = vscode.languages.createDiagnosticCollection('devicetree');
this.maxLineLength = maxLineLength + 1;
this.includeComments = includeComments;
Comment thread
mkessler001 marked this conversation as resolved.

// Read tabSize from editor configuration
const editorConfig = vscode.workspace.getConfiguration('editor');
this.tabSize = editorConfig.get<number>('tabSize', 8);
}

/**
* Update configuration settings
* @param maxLineLength The new maximum line length
* @param includeComments Whether to include comments in line length calculation
*/
updateSettings(maxLineLength: number, includeComments: boolean): void {
this.maxLineLength = maxLineLength + 1;
this.includeComments = includeComments;
}

/**
* Calculate visual length of a line considering tab stops
* @param line The line to calculate the visual length for
Expand All @@ -36,6 +48,43 @@ export class DtsDiagnosticsProvider {
return visualLength + 1;
}

/**
* Remove comments from a line for length calculation
* @param line The line to process
* @param inBlockComment Whether we're currently inside a multi-line block comment
* @returns Object with the line with comments removed and whether we're still in a block comment
*/
private removeComments(line: string, inBlockComment: boolean): { line: string; inBlockComment: boolean } {
// Handle multi-line block comment continuation
if (inBlockComment) {
const endComment = line.indexOf('*/');
if (endComment !== -1) {
line = line.substring(endComment + 2);
inBlockComment = false;
} else {
return { line: '', inBlockComment: true };
}
}

// Match strings (double/single quoted) or comments
// Process in order: strings are kept, comments are removed
const regex = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\/\*[\s\S]*?\*\/|\/\*[\s\S]*|\/\/.*/g;

const result = line.replace(regex, (match) => {
// Keep strings (start with " or ')
if (match[0] === '"' || match[0] === "'") {
return match;
}
// Block comment that doesn't close - set flag and remove rest of line
if (match.startsWith('/*') && !match.endsWith('*/')) {
inBlockComment = true;
}
return '';
});

return { line: result.trimEnd(), inBlockComment };
}

/**
* Check for lines exceeding maximum length
* @param document The document to check
Expand All @@ -45,9 +94,22 @@ export class DtsDiagnosticsProvider {
const diagnostics: vscode.Diagnostic[] = [];
const text = document.getText();
const lines = text.split('\n');
let inBlockComment = false;

lines.forEach((line, index) => {
const visualLength = this.calculateVisualLength(line);
// Calculate length based on configuration
let lineToCheck: string;

if (this.includeComments) {
lineToCheck = line;
} else {
const result = this.removeComments(line, inBlockComment);
lineToCheck = result.line;
inBlockComment = result.inBlockComment;
}

const visualLength = this.calculateVisualLength(lineToCheck);

if (visualLength > this.maxLineLength) {
const range = new vscode.Range(index, 0, index, Number.MAX_VALUE);
const diagnostic = new vscode.Diagnostic(
Expand Down
76 changes: 52 additions & 24 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import * as vscode from 'vscode';
import { DtsFormatterProvider } from './formatter';
import { DtsDiagnosticsProvider } from './diagnostics';

// Global diagnostics provider instance
// Global provider instances
let diagnosticsProvider: DtsDiagnosticsProvider | undefined;
let formatterProvider: DtsFormatterProvider | undefined;

/**
* Activate the extension
Expand All @@ -20,48 +21,44 @@ export function activate(context: vscode.ExtensionContext) {
// Get configuration
const config = vscode.workspace.getConfiguration('devicetree');
const maxLineLength = config.get<number>('maxLineLength', 80);
const enableWarnings = config.get<boolean>('enableWarnings', true);
const enableWarnings = config.get<boolean>('diagnostics.enableWarnings', true);
const includeComments = config.get<boolean>('diagnostics.lineLengthIncludeComments', true);

// Create diagnostics provider
diagnosticsProvider = new DtsDiagnosticsProvider(maxLineLength);
// Create providers
diagnosticsProvider = new DtsDiagnosticsProvider(maxLineLength, includeComments);
formatterProvider = new DtsFormatterProvider(maxLineLength);

// Register the formatter provider
context.subscriptions.push(
vscode.languages.registerDocumentFormattingEditProvider('dts', new DtsFormatterProvider(maxLineLength))
vscode.languages.registerDocumentFormattingEditProvider('dts', formatterProvider)
);

// Register diagnostics provider
context.subscriptions.push(diagnosticsProvider);
if (enableWarnings) {
// Helper function to analyze document if it's a DTS file
const analyzeIfDts = (document: vscode.TextDocument): void => {
if (document.languageId === 'dts' && diagnosticsProvider) {
void diagnosticsProvider.analyzeDocument(document);
}
};

// Listen for document changes
context.subscriptions.push(
vscode.workspace.onDidChangeTextDocument(event => {
if (event.document.languageId === 'dts' && diagnosticsProvider) {
// Debounce: analyze after a short delay to avoid excessive analysis
setTimeout(() => {
diagnosticsProvider?.analyzeDocument(event.document);
}, 500);
}
})
);
// Debounce: analyze after a short delay to avoid excessive analysis
setTimeout(() => analyzeIfDts(event.document), 500);
}));

// Listen for text editor options changes (e.g., when tab size changes in the editor)
context.subscriptions.push(
vscode.window.onDidChangeTextEditorOptions(event => {
const document = event.textEditor.document;
if (document.languageId === 'dts' && diagnosticsProvider) {
diagnosticsProvider.analyzeDocument(document);
}
analyzeIfDts(event.textEditor.document);
})
);

// Listen for document opens
context.subscriptions.push(
vscode.workspace.onDidOpenTextDocument(document => {
if (document.languageId === 'dts' && diagnosticsProvider) {
diagnosticsProvider.analyzeDocument(document);
}
})
vscode.workspace.onDidOpenTextDocument(analyzeIfDts)
);

// Listen for document closes
Expand All @@ -72,8 +69,35 @@ export function activate(context: vscode.ExtensionContext) {
}
})
);

}

// Listen for configuration changes
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration('devicetree')) {
const config = vscode.workspace.getConfiguration('devicetree');
const maxLineLength = config.get<number>('maxLineLength', 80);
const includeComments = config.get<boolean>('diagnostics.lineLengthIncludeComments', true);

// Update formatter settings
if (formatterProvider) {
formatterProvider.updateSettings(maxLineLength);
}

// Update diagnostics settings
if (diagnosticsProvider) {
diagnosticsProvider.updateSettings(maxLineLength, includeComments);

// Re-analyze all open DTS documents with new settings
vscode.workspace.textDocuments.forEach(document => {
if (document.languageId === 'dts') {
void diagnosticsProvider?.analyzeDocument(document);
}
});
}
}
})
);
}

/**
Expand All @@ -82,6 +106,10 @@ export function activate(context: vscode.ExtensionContext) {
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function deactivate() {
if (formatterProvider) {
formatterProvider.dispose();
formatterProvider = undefined;
}
if (diagnosticsProvider) {
diagnosticsProvider.dispose();
diagnosticsProvider = undefined;
Expand Down
8 changes: 8 additions & 0 deletions src/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,14 @@ export class DtsFormatterProvider implements vscode.DocumentFormattingEditProvid
this.outputChannel = vscode.window.createOutputChannel('DeviceTree');
}

/**
* Update configuration settings
* @param maxLineLength The new maximum line length
*/
updateSettings(maxLineLength: number): void {
this.maxLineLength = maxLineLength;
}

/**
* Provide formatting edits for a document
* @param document The document to format
Expand Down
95 changes: 92 additions & 3 deletions src/test/diagnostics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,95 @@ suite('DTS Diagnostics - Line Length', () => {
});
});

suite('DTS Diagnostics - Comment Handling', () => {
test('Should include comments in line length by default', async () => {
const input = '/ {\n\tmodel = "Test";\t\t\t\t\t/* This is a very long comment that makes the line exceed 80 characters */\n};';
const diagnostics = await getDiagnostics(input);
assert.ok(diagnostics.length > 0);
assert.ok(diagnostics[0].message.includes('exceeds maximum length'));
});

test('Should handle multi-line block comments correctly', async () => {
// When lineLengthIncludeComments is false, lines inside a multi-line block comment
// should be recognized as comment-only lines and not trigger warnings
const config = vscode.workspace.getConfiguration('devicetree');
const original = config.get<boolean>('diagnostics.lineLengthIncludeComments', true);

try {
await config.update('diagnostics.lineLengthIncludeComments', false, vscode.ConfigurationTarget.Global);
await new Promise(resolve => setTimeout(resolve, 300));

const input = `/ {
\t/*
\t * This is a very long comment line inside a multi-line block comment that exceeds 80 characters
\t */
\tmodel = "Test";
};`;
const diagnostics = await getDiagnostics(input);
// Should NOT warn because the long line is inside a block comment
assert.strictEqual(diagnostics.length, 0);
} finally {
await config.update('diagnostics.lineLengthIncludeComments', original, vscode.ConfigurationTarget.Global);
}
});

test('Should not treat comment-like content inside strings as comments', async () => {
const config = vscode.workspace.getConfiguration('devicetree');
const original = config.get<boolean>('diagnostics.lineLengthIncludeComments', true);

try {
await config.update('diagnostics.lineLengthIncludeComments', false, vscode.ConfigurationTarget.Global);
await new Promise(resolve => setTimeout(resolve, 300));

// The string contains "// " which looks like a comment but isn't
// A very long URL that makes the line exceed 80 chars
const input = '/ {\n\turl = "https://example.com/very/long/path/that/exceeds/the/maximum/line/length";\n};';
const diagnostics = await getDiagnostics(input);
// Should warn because the string content is NOT a comment
assert.ok(diagnostics.length > 0);
} finally {
await config.update('diagnostics.lineLengthIncludeComments', original, vscode.ConfigurationTarget.Global);
}
});

test('Should exclude comments when lineLengthIncludeComments is false', async () => {
const config = vscode.workspace.getConfiguration('devicetree');
const original = config.get<boolean>('diagnostics.lineLengthIncludeComments', true);

try {
await config.update('diagnostics.lineLengthIncludeComments', false, vscode.ConfigurationTarget.Global);
// Wait for config change to be picked up
await new Promise(resolve => setTimeout(resolve, 300));

// Line is short without the comment, but exceeds 80 with the comment
const input = '/ {\n\tmodel = "Test";\t\t\t\t\t/* This is a very long comment that makes the line exceed 80 characters */\n};';
const diagnostics = await getDiagnostics(input);
// Should NOT warn when comments are excluded
assert.strictEqual(diagnostics.length, 0);
} finally {
await config.update('diagnostics.lineLengthIncludeComments', original, vscode.ConfigurationTarget.Global);
}
});

test('Should exclude line comments (//) when lineLengthIncludeComments is false', async () => {
const config = vscode.workspace.getConfiguration('devicetree');
const original = config.get<boolean>('diagnostics.lineLengthIncludeComments', true);

try {
await config.update('diagnostics.lineLengthIncludeComments', false, vscode.ConfigurationTarget.Global);
await new Promise(resolve => setTimeout(resolve, 300));

// Line is short without the comment, but exceeds 80 with the comment
const input = '/ {\n\tmodel = "Test";\t\t\t\t\t// This is a very long line comment that makes the line exceed 80 characters\n};';
const diagnostics = await getDiagnostics(input);
// Should NOT warn when comments are excluded
assert.strictEqual(diagnostics.length, 0);
} finally {
await config.update('diagnostics.lineLengthIncludeComments', original, vscode.ConfigurationTarget.Global);
}
});
});

suite('DTS Diagnostics - Configuration', () => {
test('Should use configured maxLineLength on startup', async () => {
// Note: The maxLineLength is read once when the extension activates
Expand All @@ -130,11 +219,11 @@ suite('DTS Diagnostics - Configuration', () => {

test('Should be disabled when enableWarnings is false', async () => {
const config = vscode.workspace.getConfiguration('devicetree');
const originalEnableWarnings = config.get<boolean>('enableWarnings', true);
const originalEnableWarnings = config.get<boolean>('diagnostics.enableWarnings', true);

try {
// Disable warnings
await config.update('enableWarnings', false, vscode.ConfigurationTarget.Global);
await config.update('diagnostics.enableWarnings', false, vscode.ConfigurationTarget.Global);

// Reload extension or wait for config to apply
await new Promise(resolve => setTimeout(resolve, 200));
Expand All @@ -148,7 +237,7 @@ suite('DTS Diagnostics - Configuration', () => {
assert.ok(diagnostics.length >= 0); // Just verify it doesn't crash
} finally {
// Restore original setting
await config.update('enableWarnings', originalEnableWarnings, vscode.ConfigurationTarget.Global);
await config.update('diagnostics.enableWarnings', originalEnableWarnings, vscode.ConfigurationTarget.Global);
}
});
});
Expand Down
Loading