Skip to content

Commit

Permalink
fix: Improve error messages when a file is blocked. (#3860)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S authored Dec 3, 2024
1 parent 330f940 commit e303d1e
Show file tree
Hide file tree
Showing 18 changed files with 216 additions and 59 deletions.
3 changes: 3 additions & 0 deletions fixtures/workspaces/single/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cSpell.blockCheckingWhenTextChunkSizeGreaterThan": 400
}
12 changes: 12 additions & 0 deletions fixtures/workspaces/single/REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Weekly Report

Image: [long url](https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/29e25571-eb24-4381-9a2d-bde0ba52be2e/df3uxma-90078aec-f043-423b-8adf-68b0db323607.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcLzI5ZTI1NTcxLWViMjQtNDM4MS05YTJkLWJkZTBiYTUyYmUyZVwvZGYzdXhtYS05MDA3OGFlYy1mMDQzLTQyM2ItOGFkZi02OGIwZGIzMjM2MDcucG5nIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0.Pap7EkIxDlgZ1dFLyEK_MOlPIQGjvJVm5T8adKtnAn0)

See VS Code Setting: [cSpell.blockCheckingWhenTextChunkSizeGreaterThan](vscode://settings/cSpell.blockCheckingWhenTextChunkSizeGreaterThan)

This file is expected to be checked.

Possible very long word:
token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcLzI5ZTI1NTcxLWViMjQtNDM4MS05YTJkLWJkZTBiYTUyYmUyZVwvZGYzdXhtYS05MDA3OGFlYy1mMDQzLTQyM2ItOGFkZi02OGIwZGIzMjM2MDcucG5nIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0.Pap7EkIxDlgZ1dFLyEK_MOlPIQGjvJVm5T8adKtnAn0

tokken spellinng.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3704,7 +3704,7 @@
"properties": {
"cSpell.blockCheckingWhenAverageChunkSizeGreaterThan": {
"default": 80,
"markdownDescription": "The maximum average length of chunks of text without word breaks.\n\n\nA chunk is the characters between absolute word breaks.\nAbsolute word breaks match: `/[\\s,{}[\\]]/`\n\n\n**Error Message:** _Average Word Size is Too High._\n\n\nIf you are seeing this message, it means that the file contains mostly long lines\nwithout many word breaks.",
"markdownDescription": "The maximum average length of chunks of text without word breaks.\n\n\nA chunk is the characters between absolute word breaks.\nAbsolute word breaks match: `/[\\s,{}[\\]]/`\n\n\n**Error Message:** _Average word length is too long._\n\n\nIf you are seeing this message, it means that the file contains mostly long lines\nwithout many word breaks.",
"scope": "language-overridable",
"type": "number"
},
Expand All @@ -3716,7 +3716,7 @@
},
"cSpell.blockCheckingWhenTextChunkSizeGreaterThan": {
"default": 500,
"markdownDescription": "The maximum length of a chunk of text without word breaks.\n\n\nIt is used to prevent spell checking of generated files.\n\n\nA chunk is the characters between absolute word breaks.\nAbsolute word breaks match: `/[\\s,{}[\\]]/`, i.e. spaces or braces.\n\n\n**Error Message:** _Maximum Word Length is Too High._\n\n\nIf you are seeing this message, it means that the file contains a very long line\nwithout many word breaks.",
"markdownDescription": "The maximum length of a chunk of text without word breaks.\n\n\nIt is used to prevent spell checking of generated files.\n\n\nA chunk is the characters between absolute word breaks.\nAbsolute word breaks match: `/[\\s,{}[\\]]/`, i.e. spaces or braces.\n\n\n**Error Message:** _Maximum word length exceeded._\n\n\nIf you are seeing this message, it means that the file contains a very long line\nwithout many word breaks.",
"scope": "language-overridable",
"type": "number"
},
Expand Down
8 changes: 4 additions & 4 deletions packages/_server/spell-checker-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3377,8 +3377,8 @@
"properties": {
"cSpell.blockCheckingWhenAverageChunkSizeGreaterThan": {
"default": 80,
"description": "The maximum average length of chunks of text without word breaks.\n\n\nA chunk is the characters between absolute word breaks. Absolute word breaks match: `/[\\s,{}[\\]]/`\n\n\n**Error Message:** _Average Word Size is Too High._\n\n\nIf you are seeing this message, it means that the file contains mostly long lines without many word breaks.",
"markdownDescription": "The maximum average length of chunks of text without word breaks.\n\n\nA chunk is the characters between absolute word breaks.\nAbsolute word breaks match: `/[\\s,{}[\\]]/`\n\n\n**Error Message:** _Average Word Size is Too High._\n\n\nIf you are seeing this message, it means that the file contains mostly long lines\nwithout many word breaks.",
"description": "The maximum average length of chunks of text without word breaks.\n\n\nA chunk is the characters between absolute word breaks. Absolute word breaks match: `/[\\s,{}[\\]]/`\n\n\n**Error Message:** _Average word length is too long._\n\n\nIf you are seeing this message, it means that the file contains mostly long lines without many word breaks.",
"markdownDescription": "The maximum average length of chunks of text without word breaks.\n\n\nA chunk is the characters between absolute word breaks.\nAbsolute word breaks match: `/[\\s,{}[\\]]/`\n\n\n**Error Message:** _Average word length is too long._\n\n\nIf you are seeing this message, it means that the file contains mostly long lines\nwithout many word breaks.",
"scope": "language-overridable",
"type": "number"
},
Expand All @@ -3391,8 +3391,8 @@
},
"cSpell.blockCheckingWhenTextChunkSizeGreaterThan": {
"default": 500,
"description": "The maximum length of a chunk of text without word breaks.\n\n\nIt is used to prevent spell checking of generated files.\n\n\nA chunk is the characters between absolute word breaks. Absolute word breaks match: `/[\\s,{}[\\]]/`, i.e. spaces or braces.\n\n\n**Error Message:** _Maximum Word Length is Too High._\n\n\nIf you are seeing this message, it means that the file contains a very long line without many word breaks.",
"markdownDescription": "The maximum length of a chunk of text without word breaks.\n\n\nIt is used to prevent spell checking of generated files.\n\n\nA chunk is the characters between absolute word breaks.\nAbsolute word breaks match: `/[\\s,{}[\\]]/`, i.e. spaces or braces.\n\n\n**Error Message:** _Maximum Word Length is Too High._\n\n\nIf you are seeing this message, it means that the file contains a very long line\nwithout many word breaks.",
"description": "The maximum length of a chunk of text without word breaks.\n\n\nIt is used to prevent spell checking of generated files.\n\n\nA chunk is the characters between absolute word breaks. Absolute word breaks match: `/[\\s,{}[\\]]/`, i.e. spaces or braces.\n\n\n**Error Message:** _Maximum word length exceeded._\n\n\nIf you are seeing this message, it means that the file contains a very long line without many word breaks.",
"markdownDescription": "The maximum length of a chunk of text without word breaks.\n\n\nIt is used to prevent spell checking of generated files.\n\n\nA chunk is the characters between absolute word breaks.\nAbsolute word breaks match: `/[\\s,{}[\\]]/`, i.e. spaces or braces.\n\n\n**Error Message:** _Maximum word length exceeded._\n\n\nIf you are seeing this message, it means that the file contains a very long line\nwithout many word breaks.",
"scope": "language-overridable",
"type": "number"
},
Expand Down
8 changes: 8 additions & 0 deletions packages/_server/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
GetConfigurationTargetsResult,
GetSpellCheckingOffsetsResult,
IsSpellCheckEnabledResult,
OnBlockFile,
OnDocumentConfigChange,
OnSpellCheckDocumentStep,
PublishDiagnostics,
Expand Down Expand Up @@ -102,6 +103,13 @@ export interface ClientNotificationsAPI {
* @param notification - The notification.
*/
onDocumentConfigChange(notification: OnDocumentConfigChange): void;

/**
* Notify the client that a file is blocked from being spell checked.
* @param uri - the uri of the document.
* @param block - the reason the file is blocked.
*/
onBlockFile(notification: OnBlockFile): void;
}

export interface SpellCheckerServerAPI extends RpcAPI {
Expand Down
15 changes: 14 additions & 1 deletion packages/_server/src/api/apiModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ export type { Position, Range } from 'vscode-languageserver-types';
export interface BlockedFileReason {
code: string;
message: string;
documentationRefUri?: UriString;
documentationRefUri: UriString;
settingsUri: UriString;
settingsID: string;
notificationMessage: string;
}

export type UriString = string;
Expand Down Expand Up @@ -369,3 +372,13 @@ export interface IsSpellCheckingEnabledForUrisResponse {
export interface OnDocumentConfigChange {
uris: DocumentUri[];
}

/**
* Notify the client that a file is blocked from being spell checked.
*/
export interface OnBlockFile {
/** the uri of the document being blocked */
uri: DocumentUri;
/** the reason the file is blocked. */
reason: BlockedFileReason;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface SpellCheckerShouldCheckDocSettings {
* Absolute word breaks match: `/[\s,{}[\]]/`, i.e. spaces or braces.
*
*
* **Error Message:** _Maximum Word Length is Too High._
* **Error Message:** _Maximum word length exceeded._
*
*
* If you are seeing this message, it means that the file contains a very long line
Expand All @@ -45,7 +45,7 @@ export interface SpellCheckerShouldCheckDocSettings {
* Absolute word breaks match: `/[\s,{}[\]]/`
*
*
* **Error Message:** _Average Word Size is Too High._
* **Error Message:** _Average word length is too long._
*
*
* If you are seeing this message, it means that the file contains mostly long lines
Expand Down
20 changes: 15 additions & 5 deletions packages/_server/src/server.mts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import type { PartialServerSideHandlers } from './serverApi.mjs';
import { createServerApi } from './serverApi.mjs';
import { createOnSuggestionsHandler } from './suggestionsServer.mjs';
import { handleTraceRequest } from './trace.js';
import type { MinifiedReason } from './utils/analysis.mjs';
import { defaultIsTextLikelyMinifiedOptions, isTextLikelyMinified } from './utils/analysis.mjs';
import { catchPromise } from './utils/catchPromise.mjs';
import { debounce as simpleDebounce } from './utils/debounce.mjs';
Expand Down Expand Up @@ -524,10 +525,6 @@ export function run(): void {
blockCheckingWhenAverageChunkSizeGreaterThan = defaultIsTextLikelyMinifiedOptions.blockCheckingWhenAverageChunkSizeGreaterThan,
blockCheckingWhenTextChunkSizeGreaterThan = defaultIsTextLikelyMinifiedOptions.blockCheckingWhenTextChunkSizeGreaterThan,
} = settings;
if (blockedFiles.has(uri)) {
log(`File is blocked ${blockedFiles.get(uri)?.message}`, uri);
return true;
}
const isMiniReason =
textDocument.getText &&
isTextLikelyMinified(textDocument.getText(), {
Expand All @@ -537,9 +534,14 @@ export function run(): void {
});

if (isMiniReason) {
const notify = !blockedFiles.has(uri);
blockedFiles.set(uri, isMiniReason);
// connection.window.showInformationMessage(`File not spell checked:\n${isMiniReason}\n\"${uriToName(toUri(uri))}"`);
log(`File is blocked: ${isMiniReason.message}`, uri);
if (notify) {
notifyUserAboutBlockedFile(uri, isMiniReason);
}
} else {
blockedFiles.delete(uri);
}

return !!isMiniReason;
Expand Down Expand Up @@ -771,6 +773,14 @@ export function run(): void {
disposables.push(() => v.unsubscribe());
return v;
}

async function notifyUserAboutBlockedFile(uri: string, reason: MinifiedReason) {
try {
clientServerApi.clientNotification.onBlockFile({ uri, reason });
} catch (e) {
logError(`notifyUserAboutBlockedFile ${e}`);
}
}
}

interface TextDocumentInfo {
Expand Down
1 change: 1 addition & 0 deletions packages/_server/src/serverApi.mts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function createServerApi(connection: MessageConnection, handlers: Partial
onSpellCheckDocument: true,
onDiagnostics: true,
onDocumentConfigChange: true,
onBlockFile: true,
},
};
return createServerSideApi(connection, api, logger);
Expand Down
1 change: 1 addition & 0 deletions packages/_server/src/test/test.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function createMockServerSideApi() {
onSpellCheckDocument: vi.fn(),
onDiagnostics: vi.fn(),
onDocumentConfigChange: vi.fn(),
onBlockFile: vi.fn(),
},
clientRequest: {
onWorkspaceConfigForDocumentRequest: vi.fn(),
Expand Down
80 changes: 53 additions & 27 deletions packages/_server/src/utils/analysis.mts
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
import { genSequence } from 'gensequence';
import { opFilter, opMap, opTake, pipe } from '@cspell/cspell-pipe/sync';

import type { BlockedFileReason } from '../api.js';

export interface MinifiedReason extends BlockedFileReason {
documentationRefUri: string;
}
export type MinifiedReason = BlockedFileReason;

export const ReasonLineLength: MinifiedReason = {
code: 'Lines_too_long.',
message: 'Lines are too long.',
notificationMessage:
'For performance reasons, the spell checker does not check documents where the line length is greater than ${limit}.',
settingsUri: 'vscode://settings/cSpell.blockCheckingWhenLineLengthGreaterThan',
settingsID: 'cSpell.blockCheckingWhenLineLengthGreaterThan',
documentationRefUri:
'https://streetsidesoftware.github.io/vscode-spell-checker/docs/configuration/performance/#cspellblockcheckingwhenlinelengthgreaterthan',
'https://streetsidesoftware.com/vscode-spell-checker/docs/configuration/performance/#cspellblockcheckingwhenlinelengthgreaterthan',
};

export const ReasonAverageWordsSize: MinifiedReason = {
code: 'Word_Size_Too_High.',
message: 'Average Word Size is Too High.',
message: 'Average word length is too long.',
notificationMessage:
'For performance reasons, the spell checker does not check documents where the average block ' +
'of text without spaces or word breaks is greater than ${limit}.',
settingsUri: 'vscode://settings/cSpell.blockCheckingWhenAverageChunkSizeGreaterThan',
settingsID: 'cSpell.blockCheckingWhenAverageChunkSizeGreaterThan',
documentationRefUri:
'https://streetsidesoftware.github.io/vscode-spell-checker/docs/configuration/performance/#cspellblockcheckingwhenaveragechunksizegreaterthan',
'https://streetsidesoftware.com/vscode-spell-checker/docs/configuration/performance/#cspellblockcheckingwhenaveragechunksizegreaterthan',
};
export const ReasonMaxWordsSize: MinifiedReason = {
code: 'Maximum_Word_Length_Exceeded',
message: 'Maximum Word Length Exceeded.',
message: 'Maximum word length exceeded.',
notificationMessage:
'For performance reasons, the spell checker does not check documents with very long blocks of text ' +
'without spaces or word breaks. The limit is currently ${limit}.',
settingsUri: 'vscode://settings/cSpell.blockCheckingWhenTextChunkSizeGreaterThan',
settingsID: 'cSpell.blockCheckingWhenTextChunkSizeGreaterThan',
documentationRefUri:
'https://streetsidesoftware.github.io/vscode-spell-checker/docs/configuration/performance/#cspellblockcheckingwhentextchunksizegreaterthan',
'https://streetsidesoftware.com/vscode-spell-checker/docs/configuration/performance/#cspellblockcheckingwhentextchunksizegreaterthan',
};

export interface IsTextLikelyMinifiedOptions {
Expand All @@ -45,39 +57,53 @@ export const defaultIsTextLikelyMinifiedOptions: IsTextLikelyMinifiedOptions = {
blockCheckingWhenAverageChunkSizeGreaterThan: 80,
};

export function hydrateReason(reason: MinifiedReason, limit: number): MinifiedReason {
return {
...reason,
notificationMessage: reason.notificationMessage.replaceAll('${limit}', limit.toString()),
};
}

const ignoreUrls = /\b[a-z]{3,}:\/[-/a-z0-9@:%._+~#=?&]+/gi;

/**
* Check if a document is minified making spell checking difficult and slow.
*
* @param doc - document to check.
* @returns true - if the file might be minified.
*/
export function isTextLikelyMinified(text: string, options: IsTextLikelyMinifiedOptions): MinifiedReason | false {
const lineBreaks = [0].concat(
genSequence(text.matchAll(/\n/g))
.map((a) => a.index || 0)
.take(100)
.toArray(),
);
if (lineBreaks.length < 100) lineBreaks.push(text.length);
const first100 = getFirstNLinesWithText(text, 100).map((a) => a.replace(ignoreUrls, ''));

const first100 = genSequence(lineBreaks)
.scan((a, b) => [a[1], b], [0, 0])
.map(([a, b]) => text.slice(a, b).trim())
.filter((a) => !!a)
.toArray();

const over1k = genSequence(first100).first((a) => a.length > options.blockCheckingWhenLineLengthGreaterThan);
if (over1k) return ReasonLineLength;
const over1k = first100.find((a) => a.length > options.blockCheckingWhenLineLengthGreaterThan);
if (over1k) {
return hydrateReason(ReasonLineLength, options.blockCheckingWhenLineLengthGreaterThan);
}

const sampleText = first100.join('\n');
const chunks = [...sampleText.matchAll(/[\s,{}[\]]+/g)].map((a) => a.index || 0);
const chunks = [...sampleText.matchAll(/[\s,{}[\]/]+/g)].map((a) => a.index || 0);
chunks.push(sampleText.length);
const wordCount = chunks.length;
const avgChunkSize = sampleText.length / wordCount;
if (avgChunkSize > options.blockCheckingWhenAverageChunkSizeGreaterThan) return ReasonAverageWordsSize;
if (avgChunkSize > options.blockCheckingWhenAverageChunkSizeGreaterThan) {
return hydrateReason(ReasonAverageWordsSize, options.blockCheckingWhenAverageChunkSizeGreaterThan);
}

const maxChunkSize = chunks.reduce((a, b) => [b, Math.max(a[1], b - a[0])], [0, 0])[1];
if (maxChunkSize > options.blockCheckingWhenTextChunkSizeGreaterThan) return ReasonMaxWordsSize;
if (maxChunkSize > options.blockCheckingWhenTextChunkSizeGreaterThan) {
return hydrateReason(ReasonMaxWordsSize, options.blockCheckingWhenTextChunkSizeGreaterThan);
}

return false;
}

export function getFirstNLinesWithText(text: string, n: number): string[] {
return [
...pipe(
text.matchAll(/^.*$/gm),
opMap((a) => a[0].trim()),
opFilter((a) => !!a),
opTake(n),
),
];
}
Loading

0 comments on commit e303d1e

Please sign in to comment.