This commit is contained in:
Chloe Byun
2026-04-29 12:47:01 -07:00
parent c8ea962457
commit 5290ada3dd
2 changed files with 43 additions and 5 deletions
@@ -371,6 +371,11 @@ describe('validateCustomReadCSVFunction', () => {
expect(result.valid).toBe(true)
})
it('should allow spaces around keyword equals', () => {
const result = validateCustomReadCSVFunction("read_csv(csv_data, sep = ';', header = 0)")
expect(result.valid).toBe(true)
})
it('should allow statement separator characters inside string literals', () => {
const result = validateCustomReadCSVFunction("read_csv(csv_data, sep=';', lineterminator='\\n')")
expect(result.valid).toBe(true)
@@ -381,6 +386,11 @@ describe('validateCustomReadCSVFunction', () => {
expect(result.valid).toBe(true)
})
it('should allow spaces around dictionary colons and a trailing dictionary comma', () => {
const result = validateCustomReadCSVFunction("read_csv(csv_data, na_values={'Age' : ['NA'],})")
expect(result.valid).toBe(true)
})
it('should allow filepath_or_buffer=csv_data as the explicit source', () => {
const result = validateCustomReadCSVFunction("read_csv(filepath_or_buffer=csv_data, encoding='utf-8')")
expect(result.valid).toBe(true)
@@ -444,6 +454,12 @@ describe('validateCustomReadCSVFunction', () => {
expect(result.reason).toContain('duplicated')
})
it('should block filepath_or_buffer override when source is positional', () => {
const result = validateCustomReadCSVFunction("read_csv(csv_data, filepath_or_buffer='other.csv')")
expect(result.valid).toBe(false)
expect(result.reason).toContain('duplicated')
})
it('should block malformed bracket nesting', () => {
const result = validateCustomReadCSVFunction("read_csv(csv_data, usecols=['Name', 'Age')")
expect(result.valid).toBe(false)
+27 -5
View File
@@ -139,9 +139,29 @@ function splitTopLevel(code: string, delimiter: string): string[] | undefined {
}
function findTopLevelCharacter(code: string, character: string): number {
const parts = splitTopLevel(code, character)
if (!parts || parts.length < 2) return -1
return parts[0].length
const bracketStack: string[] = []
for (let i = 0; i < code.length; i += 1) {
const char = code[i]
if (char === "'" || char === '"') {
const nextIndex = skipQuotedString(code, i)
if (nextIndex < 0) return -1
i = nextIndex - 1
continue
}
const expectedClosing = expectedClosingBracket(char)
if (expectedClosing) {
bracketStack.push(expectedClosing)
} else if (char === ')' || char === ']' || char === '}') {
if (bracketStack.pop() !== char) return -1
} else if (char === character && bracketStack.length === 0) {
return i
}
}
return -1
}
function containsStatementSeparatorOutsideString(code: string): boolean {
@@ -217,7 +237,9 @@ function isSafeReadCSVLiteral(code: string): boolean {
const entries = inner ? splitTopLevel(inner, ',') : []
if (!entries) return false
return entries.every((entry) => {
return entries.every((entry, index) => {
if (!entry && index === entries.length - 1) return true
const colonIndex = findTopLevelCharacter(entry, ':')
if (colonIndex < 0) return false
@@ -252,7 +274,7 @@ function validateReadCSVArguments(args: string): PythonCodeValidationResult {
}
}
const seenKeywords = new Set(sourceKeyword ? [sourceKeyword] : [])
const seenKeywords = new Set(['filepath_or_buffer'])
for (const arg of remainingArgs) {
if (!arg) {
return { valid: false, reason: 'read_csv() options must not contain empty arguments' }