Tag-based and bare-script CFML, extends/implements, <cfscript>/<cfquery> delegation, BOM + unquoted-attribute handling. Wasm grammars verified bit-for-bit reproducible from cfmleditor/tree-sitter-cfml. Validated on FW/1, ColdBox, CFWheels. Follow-up: #1152. Co-authored-by: ghedwards <125586+ghedwards@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
ghedwards
Claude Fable 5
parent
cc89146454
commit
816bacb7f2
@@ -0,0 +1,26 @@
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
/**
|
||||
* `<cfquery>` SQL bodies: `#hash#` expressions inside the SQL text are real
|
||||
* CFML expressions (tree-sitter-cfml's `cfquery` grammar parses them
|
||||
* structurally — `call_expression`/`member_expression`, same shape as
|
||||
* cfscript's), so a call like `#getCurrentUser().getId()#` embedded in a
|
||||
* WHERE clause is a genuine call edge. The surrounding SQL keywords/
|
||||
* identifiers aren't symbols CodeGraph models — only `call_expression` is
|
||||
* mapped, so extraction yields call references and nothing else.
|
||||
*/
|
||||
export const cfqueryExtractor: LanguageExtractor = {
|
||||
functionTypes: [],
|
||||
classTypes: [],
|
||||
methodTypes: [],
|
||||
interfaceTypes: [],
|
||||
structTypes: [],
|
||||
enumTypes: [],
|
||||
typeAliasTypes: [],
|
||||
importTypes: [],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: [],
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
/** CFML access modifiers (`public`/`private`/`package`/`remote`) on a function_declaration. */
|
||||
function cfmlVisibility(node: SyntaxNode): 'public' | 'private' | 'protected' | 'internal' | undefined {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'access_type') {
|
||||
const text = child.text;
|
||||
if (text === 'public') return 'public';
|
||||
if (text === 'private') return 'private';
|
||||
if (text === 'package') return 'internal';
|
||||
if (text === 'remote') return 'public';
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const cfscriptExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_declaration', 'function_expression', 'arrow_function'],
|
||||
classTypes: ['component'],
|
||||
// `component` is reused for both `component { ... }` and `interface { ... }` —
|
||||
// the only difference is the literal first token (verified via the grammar's
|
||||
// native binding: child(0).type is 'component' or 'interface', both unnamed).
|
||||
classifyClassNode: (node) => (node.child(0)?.type === 'interface' ? 'interface' : 'class'),
|
||||
methodTypes: ['function_declaration', 'method_definition'],
|
||||
interfaceTypes: [],
|
||||
structTypes: [],
|
||||
enumTypes: [],
|
||||
typeAliasTypes: [],
|
||||
importTypes: ['import_statement', 'include_statement'],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: ['variable_declaration'],
|
||||
propertyTypes: ['property_declaration'],
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
getVisibility: cfmlVisibility,
|
||||
getSignature: (node, source) => {
|
||||
const params = getChildByField(node, 'parameters');
|
||||
return params ? getNodeText(params, source) : undefined;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
|
||||
if (node.type === 'include_statement') {
|
||||
// `include "path/to/file.cfm";` — the included template path.
|
||||
const expr = node.namedChildren.find((c: SyntaxNode) => c.type === 'string');
|
||||
if (!expr) return null;
|
||||
const moduleName = getNodeText(expr, source).replace(/^["']|["']$/g, '');
|
||||
return moduleName ? { moduleName, signature: importText } : null;
|
||||
}
|
||||
|
||||
// `import com.foo.Bar;` (dotted path) or `import "java:java.util.ArrayList";` (string form)
|
||||
const sourceNode = getChildByField(node, 'source');
|
||||
if (!sourceNode) return null;
|
||||
|
||||
let moduleName: string;
|
||||
if (sourceNode.type === 'import_path') {
|
||||
moduleName = sourceNode.namedChildren
|
||||
.map((c: SyntaxNode) => getNodeText(c, source))
|
||||
.join('.');
|
||||
} else {
|
||||
moduleName = getNodeText(sourceNode, source).replace(/^["']|["']$/g, '');
|
||||
}
|
||||
return moduleName ? { moduleName, signature: importText } : null;
|
||||
},
|
||||
};
|
||||
@@ -27,6 +27,8 @@ import { luaExtractor } from './lua';
|
||||
import { rExtractor } from './r';
|
||||
import { luauExtractor } from './luau';
|
||||
import { objcExtractor } from './objc';
|
||||
import { cfscriptExtractor } from './cfscript';
|
||||
import { cfqueryExtractor } from './cfquery';
|
||||
|
||||
export const EXTRACTORS: Partial<Record<Language, LanguageExtractor>> = {
|
||||
typescript: typescriptExtractor,
|
||||
@@ -51,4 +53,6 @@ export const EXTRACTORS: Partial<Record<Language, LanguageExtractor>> = {
|
||||
r: rExtractor,
|
||||
luau: luauExtractor,
|
||||
objc: objcExtractor,
|
||||
cfscript: cfscriptExtractor,
|
||||
cfquery: cfqueryExtractor,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user