mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 07:04:04 -05:00
Add Claude Explain feature for AI-powered code explanations This PR introduces Claude Explain, a new feature that provides AI-powered explanations of compiler output directly within Compiler Explorer. Key features: Claude Explain functionality: - New explain view pane - Explains compiler output with full context of source code and compilation output - Configurable audience level and explanation type - Response caching to improve performance and reduce API calls - Usage statistics display showing requests used and token counts User experience: - Consent flow on first use explaining data handling and privacy - AI disclaimer banner warning about potential LLM inaccuracies - Respects "no-ai" directive in source code for users who don't want AI processing Privacy and security: - Data sent to Anthropic's Claude API as documented in privacy policy - No data used for model training - Clear consent required before first use - Support for opting out via "no-ai" directive The feature is marked as beta and can be enabled via configuration. Co-authored-by: Claude <noreply@anthropic.com>
196 lines
7.8 KiB
TypeScript
196 lines
7.8 KiB
TypeScript
// Copyright (c) 2023, Compiler Explorer Authors
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import {describe, expect, it} from 'vitest';
|
|
|
|
import {addDigitSeparator, capitaliseFirst, escapeHTML, splitArguments} from '../shared/common-utils.js';
|
|
|
|
describe('HTML Escape Test Cases', () => {
|
|
it('should prevent basic injection', () => {
|
|
expect(escapeHTML("<script>alert('hi');</script>")).toEqual(
|
|
'<script>alert('hi');</script>',
|
|
);
|
|
});
|
|
it('should prevent tag injection', () => {
|
|
expect(escapeHTML('\'"`>')).toEqual(''"`>');
|
|
});
|
|
});
|
|
|
|
describe('digit separator', () => {
|
|
it('handles short numbers', () => {
|
|
expect(addDigitSeparator('42', '_', 3)).toEqual('42');
|
|
});
|
|
it('handles long numbers', () => {
|
|
expect(addDigitSeparator('1234', '_', 3)).toEqual('1_234');
|
|
expect(addDigitSeparator('123456789', "'", 3)).toEqual("123'456'789");
|
|
expect(addDigitSeparator('1234567890', "'", 3)).toEqual("1'234'567'890");
|
|
});
|
|
it('handles hex numbers', () => {
|
|
expect(addDigitSeparator('AABBCCDD12345678', '_', 4)).toEqual('AABB_CCDD_1234_5678');
|
|
expect(addDigitSeparator('01AABBCCDD12345678', '_', 4)).toEqual('01_AABB_CCDD_1234_5678');
|
|
});
|
|
it('handles negative numbers', () => {
|
|
expect(addDigitSeparator('-42', '_', 3)).toEqual('-42');
|
|
expect(addDigitSeparator('-420', '_', 3)).toEqual('-420');
|
|
expect(addDigitSeparator('-4200', '_', 3)).toEqual('-4_200');
|
|
expect(addDigitSeparator('-123456789', '_', 3)).toEqual('-123_456_789');
|
|
});
|
|
});
|
|
|
|
describe('argument splitting', () => {
|
|
it('should handle empty strings', () => {
|
|
expect(splitArguments('')).toEqual([]);
|
|
});
|
|
it('should handle purely whitespace strings', () => {
|
|
expect(splitArguments(' ')).toEqual([]);
|
|
});
|
|
it('should handle normal things', () => {
|
|
expect(splitArguments('-hello --world etc --std=c++20')).toEqual(['-hello', '--world', 'etc', '--std=c++20']);
|
|
});
|
|
|
|
it('should handle hash chars', () => {
|
|
expect(splitArguments('-Wno#warnings -Wno-#pragma-messages')).toEqual([
|
|
'-Wno#warnings',
|
|
'-Wno-#pragma-messages',
|
|
]);
|
|
});
|
|
|
|
it('should handle doublequoted args', () => {
|
|
expect(splitArguments('--hello "-world etc"')).toEqual(['--hello', '-world etc']);
|
|
});
|
|
|
|
it('should handle singlequoted args', () => {
|
|
expect(splitArguments("--hello '-world etc'")).toEqual(['--hello', '-world etc']);
|
|
});
|
|
|
|
it('should handle mismatched doublequoted args', () => {
|
|
expect(splitArguments('--hello "-world etc')).toEqual(['--hello', '-world etc']);
|
|
});
|
|
|
|
it('should handle mismatched singlequoted args', () => {
|
|
expect(splitArguments("--hello '-world etc")).toEqual(['--hello', '-world etc']);
|
|
});
|
|
|
|
it('should handle hashes at the beginning of words', () => {
|
|
expect(splitArguments('hello #veryfancy etc')).toEqual(['hello', '#veryfancy', 'etc']);
|
|
});
|
|
|
|
it('should handle empty arguments', () => {
|
|
expect(splitArguments('nothing "" is lost \'\'')).toEqual(['nothing', '', 'is', 'lost', '']);
|
|
});
|
|
|
|
it('should handle weird characters', () => {
|
|
expect(splitArguments('hello | moose | \\n < yay & \n\t\nmoo')).toEqual([
|
|
'hello',
|
|
'|',
|
|
'moose',
|
|
'|',
|
|
'n',
|
|
'<',
|
|
'yay',
|
|
'&',
|
|
'moo',
|
|
]);
|
|
});
|
|
|
|
it('should handle escaped backslashes', () => {
|
|
expect(splitArguments('hello \\\\ moose')).toEqual(['hello', '\\', 'moose']);
|
|
});
|
|
|
|
it('should handle mismatched backslashes', () => {
|
|
expect(splitArguments('hello \\')).toEqual(['hello', '\\']);
|
|
expect(splitArguments('hello world\\')).toEqual(['hello', 'world\\']);
|
|
});
|
|
it('should handle mismatched backslashes in quotes', () => {
|
|
expect(splitArguments('hello "\\')).toEqual(['hello', '\\']);
|
|
});
|
|
|
|
it('should handle escaped quotes', () => {
|
|
expect(splitArguments('hi \\"mum\\"')).toEqual(['hi', '"mum"']);
|
|
expect(splitArguments('hi "\\"mum\\""')).toEqual(['hi', '"mum"']);
|
|
expect(splitArguments("hi \\'mum\\'")).toEqual(['hi', "'mum'"]);
|
|
});
|
|
|
|
it('should do something sane in the face of escaped quotes in quotes', () => {
|
|
// Not really valid input, bash sees this as a bunch of abutting single-quotes, unterminated.
|
|
// But this makes as much sense as anything and so I've added it to lock in the behaviour.
|
|
expect(splitArguments("hi '\\'mum\\''")).toEqual(['hi', "\\mum'"]);
|
|
});
|
|
|
|
it('should handle abutting quotes', () => {
|
|
expect(splitArguments('well "hello""there"')).toEqual(['well', 'hellothere']);
|
|
expect(splitArguments('well "hello"\'there\'')).toEqual(['well', 'hellothere']);
|
|
});
|
|
|
|
it('should ignore escaped hashes etc', () => {
|
|
expect(splitArguments('hello \\#veryfancy etc')).toEqual(['hello', '#veryfancy', 'etc']);
|
|
});
|
|
|
|
it('should handle multiple spaces', () => {
|
|
expect(splitArguments('hello world')).toEqual(['hello', 'world']);
|
|
});
|
|
|
|
it('should handle escaped spaces', () => {
|
|
expect(splitArguments('hello\\ there')).toEqual(['hello there']);
|
|
});
|
|
|
|
it('should handle mixed quotes and spaces', () => {
|
|
expect(splitArguments('"hello \'world\' there"')).toEqual(["hello 'world' there"]);
|
|
});
|
|
|
|
it('should handle quotes containing spaces', () => {
|
|
expect(splitArguments('hello " " world')).toEqual(['hello', ' ', 'world']);
|
|
});
|
|
|
|
it('should handle multiple escapes in quotes', () => {
|
|
expect(splitArguments('"hello \\"world\\" \\\\"')).toEqual(['hello "world" \\']);
|
|
});
|
|
});
|
|
|
|
describe('capitalise first', () => {
|
|
it('should capitalise normal strings', () => {
|
|
expect(capitaliseFirst('hello')).toEqual('Hello');
|
|
expect(capitaliseFirst('world')).toEqual('World');
|
|
});
|
|
|
|
it('should handle empty strings', () => {
|
|
expect(capitaliseFirst('')).toEqual('');
|
|
});
|
|
|
|
it('should handle single characters', () => {
|
|
expect(capitaliseFirst('a')).toEqual('A');
|
|
expect(capitaliseFirst('z')).toEqual('Z');
|
|
});
|
|
|
|
it('should handle already capitalised strings', () => {
|
|
expect(capitaliseFirst('Hello')).toEqual('Hello');
|
|
expect(capitaliseFirst('WORLD')).toEqual('WORLD');
|
|
});
|
|
|
|
it('should handle non-alphabetic first characters', () => {
|
|
expect(capitaliseFirst('123abc')).toEqual('123abc');
|
|
expect(capitaliseFirst('!hello')).toEqual('!hello');
|
|
});
|
|
});
|