Skip to content

Commit 6017541

Browse files
committed
Implement code compaction utility for C-style and Python files to strip comments and function bodies
1 parent c24abed commit 6017541

29 files changed

Lines changed: 425 additions & 0 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { compact_file } from './compact-file'
2+
import * as fs from 'fs'
3+
import * as path from 'path'
4+
5+
describe('compact_file', () => {
6+
const load_test_case = (
7+
test_case: string,
8+
directory = 'c-style'
9+
): { input: string; expected: string } => {
10+
const case_path = path.join(__dirname, 'test-cases', directory, test_case)
11+
const input = fs.readFileSync(path.join(case_path, 'input.txt'), 'utf-8')
12+
const expected = fs.readFileSync(
13+
path.join(case_path, 'output.txt'),
14+
'utf-8'
15+
)
16+
return { input, expected }
17+
}
18+
19+
it('should remove single line comments', () => {
20+
const { input, expected } = load_test_case('remove-single-line-comments')
21+
expect(compact_file(input, '.ts')).toBe(expected)
22+
})
23+
24+
it('should remove block comments on a single line', () => {
25+
const { input, expected } = load_test_case(
26+
'remove-block-comments-single-line'
27+
)
28+
expect(compact_file(input, '.ts')).toBe(expected)
29+
})
30+
31+
it('should remove multi-line block comments', () => {
32+
const { input, expected } = load_test_case(
33+
'remove-multi-line-block-comments'
34+
)
35+
expect(compact_file(input, '.ts')).toBe(expected)
36+
})
37+
38+
it('should preserve comments inside double quotes', () => {
39+
const { input, expected } = load_test_case('preserve-double-quotes')
40+
expect(compact_file(input, '.ts')).toBe(expected)
41+
})
42+
43+
it('should preserve comments inside single quotes', () => {
44+
const { input, expected } = load_test_case('preserve-single-quotes')
45+
expect(compact_file(input, '.ts')).toBe(expected)
46+
})
47+
48+
it('should preserve comments inside template literals', () => {
49+
const { input, expected } = load_test_case('preserve-template-literals')
50+
expect(compact_file(input, '.ts')).toBe(expected)
51+
})
52+
53+
it('should preserve block comments inside strings', () => {
54+
const { input, expected } = load_test_case(
55+
'preserve-block-comments-in-strings'
56+
)
57+
expect(compact_file(input, '.ts')).toBe(expected)
58+
})
59+
60+
it('should handle escaped quotes inside strings', () => {
61+
const { input, expected } = load_test_case('handle-escaped-quotes')
62+
expect(compact_file(input, '.ts')).toBe(expected)
63+
})
64+
65+
it('should handle complex mixed content', () => {
66+
const { input, expected } = load_test_case('complex-mixed-content')
67+
expect(compact_file(input, '.ts')).toBe(expected)
68+
})
69+
70+
it('should handle input with only comments', () => {
71+
const { input, expected } = load_test_case('only-comments')
72+
expect(compact_file(input, '.ts')).toBe(expected)
73+
})
74+
75+
it('should strip function bodies when enabled', () => {
76+
const { input, expected } = load_test_case('javascript-bodies')
77+
expect(compact_file(input, '.ts')).toBe(expected)
78+
})
79+
80+
it('should strip python function bodies', () => {
81+
const { input, expected } = load_test_case('python', '.')
82+
expect(compact_file(input, '.py')).toBe(expected)
83+
})
84+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { compact_c_style } from './languages/c-style'
2+
import { compact_python } from './languages/python'
3+
4+
export const compact_file = (content: string, extension: string): string => {
5+
if (extension == '.py') {
6+
return compact_python(content)
7+
}
8+
9+
return compact_c_style(content, true)
10+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './compact-file'
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
export const compact_c_style = (
2+
content: string,
3+
should_strip_bodies: boolean
4+
): string => {
5+
// Split by newline to process line by line, handling both LF and CRLF
6+
const lines = content.split(/\r?\n/)
7+
const result: string[] = []
8+
let is_in_block_comment = false
9+
let skip_body_depth = 0
10+
// Buffer to track context for stripping bodies (e.g. "class Foo ")
11+
let last_code_buffer = ''
12+
13+
for (const line of lines) {
14+
let processed_line = ''
15+
let i = 0
16+
let is_in_string: false | '"' | "'" | '`' = false
17+
18+
while (i < line.length) {
19+
const char = line[i]
20+
const next_char = line[i + 1]
21+
22+
// Handle Block Comments
23+
if (is_in_block_comment) {
24+
if (char === '*' && next_char === '/') {
25+
is_in_block_comment = false
26+
i += 2
27+
} else {
28+
i++
29+
}
30+
continue
31+
}
32+
33+
// Handle Strings (ignore comments inside strings)
34+
if (is_in_string) {
35+
if (char == '\\') {
36+
// Handle escaped characters inside string
37+
if (skip_body_depth === 0) {
38+
processed_line += char + (next_char || '')
39+
}
40+
last_code_buffer += char + (next_char || '')
41+
i += 2
42+
continue
43+
}
44+
if (char === is_in_string) {
45+
is_in_string = false
46+
}
47+
if (skip_body_depth === 0) {
48+
processed_line += char
49+
}
50+
last_code_buffer += char
51+
i++
52+
continue
53+
}
54+
55+
// Start of String
56+
if (char === '"' || char === "'" || char === '`') {
57+
is_in_string = char
58+
if (skip_body_depth === 0) {
59+
processed_line += char
60+
}
61+
last_code_buffer += char
62+
i++
63+
continue
64+
}
65+
66+
// Start of Line Comment
67+
if (char == '/' && next_char == '/') {
68+
break // Ignore the rest of the line
69+
}
70+
71+
// Start of Block Comment
72+
if (char == '/' && next_char == '*') {
73+
is_in_block_comment = true
74+
i += 2
75+
continue
76+
}
77+
78+
// Handle Function Body Stripping
79+
if (should_strip_bodies && char == '{') {
80+
if (skip_body_depth > 0) {
81+
// Already skipping, just deepen the level
82+
skip_body_depth++
83+
last_code_buffer += char
84+
i++
85+
continue
86+
} else {
87+
// Check if we should preserve this block (class, interface, etc.)
88+
// We look at last_code_buffer for keywords
89+
const is_keeper =
90+
/(^|\s)(class|interface|enum|namespace|type)(\s|$)/.test(
91+
last_code_buffer
92+
)
93+
// Also preserve object literals (preceded by = or :)
94+
const is_object_literal = /(=|:)\s*$/.test(last_code_buffer.trimEnd())
95+
96+
if (!is_keeper && !is_object_literal) {
97+
// Start skipping
98+
skip_body_depth = 1
99+
const indent = processed_line.match(/^\s*/)?.[0] || ''
100+
processed_line += `{\n${indent} // ...\n${indent}}`
101+
last_code_buffer += '{}'
102+
i++
103+
continue
104+
} else {
105+
// Keeper: reset buffer to avoid false positives in nested scopes
106+
last_code_buffer = ''
107+
}
108+
}
109+
} else if (should_strip_bodies && char == '}') {
110+
if (skip_body_depth > 0) {
111+
skip_body_depth--
112+
last_code_buffer += char
113+
i++
114+
continue
115+
}
116+
}
117+
118+
// Regular character
119+
if (skip_body_depth == 0) {
120+
processed_line += char
121+
}
122+
last_code_buffer += char
123+
i++
124+
}
125+
126+
// We trim the line to actually "compact" the file.
127+
// If a line contained only comments, it becomes empty and is skipped.
128+
const trimmed = processed_line.trim()
129+
if (trimmed) {
130+
result.push(trimmed)
131+
}
132+
133+
// Add a space to buffer for newline to avoid concatenation issues (e.g. "class Foo" + "{")
134+
last_code_buffer += ' '
135+
// Limit buffer size to avoid memory issues, keeping enough for context
136+
if (last_code_buffer.length > 200) {
137+
last_code_buffer = last_code_buffer.slice(-200)
138+
}
139+
}
140+
141+
return result.length > 0 ? result.join('\n') + '\n' : ''
142+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
export const compact_python = (content: string): string => {
2+
const lines = content.split(/\r?\n/)
3+
const result: string[] = []
4+
let skip_indent_threshold = -1
5+
6+
for (const line of lines) {
7+
// 1. Calculate Indentation
8+
const match = line.match(/^\s*/)
9+
const current_indent = match ? match[0].length : 0
10+
11+
// 2. Handle Skipping (Body Stripping)
12+
if (skip_indent_threshold !== -1) {
13+
if (current_indent > skip_indent_threshold) {
14+
continue
15+
}
16+
skip_indent_threshold = -1
17+
}
18+
19+
// 3. Strip Comments (simplified char loop to handle strings)
20+
let processed_line = ''
21+
let in_string: string | false = false
22+
let i = 0
23+
24+
while (i < line.length) {
25+
const char = line[i]
26+
27+
if (in_string) {
28+
if (char === '\\') {
29+
processed_line += char + (line[i + 1] || '')
30+
i += 2
31+
continue
32+
}
33+
// Check for end of string (handling triple quotes)
34+
if (
35+
char === in_string[0] &&
36+
(in_string.length === 1 ||
37+
line.substring(i, i + in_string.length) === in_string)
38+
) {
39+
processed_line += in_string
40+
i += in_string.length
41+
in_string = false
42+
continue
43+
}
44+
processed_line += char
45+
i++
46+
} else {
47+
// Start of String
48+
if (char === '"' || char === "'") {
49+
const is_triple = line.substring(i, i + 3) === char.repeat(3)
50+
in_string = is_triple ? char.repeat(3) : char
51+
processed_line += in_string
52+
i += in_string.length
53+
continue
54+
}
55+
// Start of Comment
56+
if (char === '#') {
57+
break // Ignore rest of line
58+
}
59+
processed_line += char
60+
i++
61+
}
62+
}
63+
64+
const trimmed = processed_line.trim()
65+
if (!trimmed) continue
66+
67+
// 4. Check for Block Start to Strip
68+
if (trimmed.endsWith(':')) {
69+
// Keep 'class', strip others (def, if, while, etc.)
70+
const is_keeper = /^class\s/.test(trimmed)
71+
if (!is_keeper) {
72+
result.push(processed_line)
73+
result.push(line.substring(0, current_indent) + ' ' + '# ...')
74+
skip_indent_threshold = current_indent
75+
continue
76+
}
77+
}
78+
79+
result.push(processed_line)
80+
}
81+
82+
return result.join('\n') + '\n'
83+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function test() {
2+
const url = "http://example.com"; // URL with //
3+
/* Start block
4+
End block */
5+
return `Result: ${url}`;
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function test() {
2+
// ...
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const str = "String with \"// quote"; // real comment
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const str = "String with \"// quote";
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
function add(a, b) {
2+
return a + b;
3+
}
4+
5+
class Calculator {
6+
constructor() {
7+
this.value = 0;
8+
}
9+
10+
add(n) {
11+
this.value += n;
12+
}
13+
}
14+
15+
interface Item {
16+
id: string;
17+
name: string;
18+
}
19+
20+
const double = (x) => {
21+
return x * 2;
22+
}
23+
24+
const config = {
25+
env: "production",
26+
debug: false
27+
};

0 commit comments

Comments
 (0)