This is an automated email from the ASF dual-hosted git repository.
Harbs pushed a commit to branch codegraph
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git
The following commit(s) were added to refs/heads/codegraph by this push:
new 15511ad2b feat(codegraph): Implement Code Graph Exporter with JSON
Schema validation
15511ad2b is described below
commit 15511ad2bbd6b15b0f57d545f50e8b0cd9ab8c67
Author: Harbs <[email protected]>
AuthorDate: Fri Jul 31 16:54:08 2026 +0300
feat(codegraph): Implement Code Graph Exporter with JSON Schema validation
- Added CODEGRAPH implementation plan with detailed schema compatibility
policy.
- Introduced `codegraph.jar` in the build process and created corresponding
scripts for Unix and Windows.
- Integrated CODEGRAPH tool into RoyaleToolGroup for seamless usage.
- Developed CodeGraphExporter and CodeGraphWriter classes to handle graph
generation and output.
- Created JSON Schema for code graph validation and added tests to ensure
compliance.
- Enhanced TestCODEGRAPH to validate tool registration and output
correctness.
- Added Maven plugin for compiling code graphs with configurable output
options.
- Implemented configuration files for JS and SWF code graph compilation.
---
CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md | 31 ++--
compiler-jx/build.xml | 16 +++
compiler-jx/pom.xml | 12 ++
compiler-jx/src/assembly/scripts/codegraph | 62 ++++++++
compiler-jx/src/assembly/scripts/codegraph.bat | 24 ++++
.../apache/royale/compiler/clients/CODEGRAPH.java | 19 ++-
.../royale/compiler/clients/RoyaleToolGroup.java | 1 +
.../internal/codegen/graph/CodeGraphExporter.java | 21 ++-
.../internal/codegen/graph/CodeGraphWriter.java | 3 +
.../codegen/graph/codegraph-1.0.schema.json | 158 +++++++++++++++++++++
.../royale/compiler/clients/TestCODEGRAPH.java | 45 +++++-
.../codegen/graph/TestCodeGraphWriter.java | 77 ++++++++++
.../test/resources/codegraph/InvalidCodeGraph.as | 2 +-
.../apache/royale/maven/CompileCodeGraphMojo.java | 156 ++++++++++++++++++++
.../config/compile-codegraph-js-config.xml | 90 ++++++++++++
.../config/compile-codegraph-swf-config.xml | 91 ++++++++++++
16 files changed, 786 insertions(+), 22 deletions(-)
diff --git a/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md
b/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md
index 7239db7db..ca1aa1f46 100644
--- a/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md
+++ b/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md
@@ -292,19 +292,28 @@ Passing the existing suite is necessary but not
sufficient: the new client must
Implemented and covered by focused compiler-backed tests:
- Public classes, interfaces, implicit interface members, package definitions,
and directly declared public members.
-- Expanded directory-valued include sources and unreferenced included source
files.
+- Expanded directory-valued include sources and unreferenced included source
files, with or without a positional target file.
- Stable IDs, deterministic JSON, source-root-relative provenance, module
identity, and external library origins.
- Resolved and explicitly unresolved signature references, with compiler
diagnostics retained for unresolved types.
- Base, interface, override, and implementation relationships.
- Visibility, declaration modifiers, parameter defaults, and variable/constant
initial values.
- Parsed ASDoc, `@private` exclusion, structured metadata, metadata ASDoc, and
type-bearing metadata references.
- JS/SWF conditional selection, compiler error gating, two-run determinism,
and an exact golden document.
+- A packaged Draft 2020-12 JSON Schema with validation of unit-built and
compiler-backed graph documents.
-Deferred extensions remain effective inherited-member views, `@copy`
resolution, JSON Schema/versioning policy, and aggregate build-tool integration.
+`@copy` targets remain structured, opaque tag values, matching Royale's
existing parser and emitter semantics. Effective inherited-member views remain
derivable from explicit base/interface edges. Aggregate build-tool integration
remains deferred.
+
+### Schema Compatibility Policy
+
+- `schemaVersion` uses `major.minor` numbering and identifies the graph
contract, independently of compiler releases.
+- Additive optional fields and new enum values require a minor version.
Removing, renaming, changing the meaning of a field, or making an optional
field required requires a major version.
+- Each supported contract has a packaged, immutable schema named
`codegraph-<major>.<minor>.schema.json` beside the graph model classes.
+- Writers emit one exact schema version. Consumers must reject unsupported
major versions and may accept newer minor versions only when they tolerate
unknown optional fields and enum values.
+- The schema, model version constant, writer output, and canonical golden
document must change together and pass schema validation.
## Suggested Pull Request Sequence
-### PR 1: Semantic exporter MVP
+### Slice 1: Semantic exporter MVP
- Graph model and deterministic JSON writer.
- Compiler-backed collection of public types and directly declared members.
@@ -312,22 +321,22 @@ Deferred extensions remain effective inherited-member
views, `@copy` resolution,
- JS/SWF fixture tests. Implemented with opposite compiler define sets that
verify target labels and active-member selection.
- CLI entry point in `compiler-jx`.
-### PR 2: Completeness
+### Slice 2: Completeness
- Package-level definitions. Implemented with focused
function/variable/constant coverage and a compiler-backed reachable
package-function fixture.
- External and unresolved symbol records. Implemented with explicit unresolved
markers, origins, and compiler-problem coverage.
- Inheritance/override edges. Implemented for base types, interfaces,
overridden methods, and interface implementations.
- Effective inherited public member view if clients require it.
-- `@copy` resolution.
-- JSON Schema and schema compatibility policy.
+- `@copy` is preserved as an opaque structured tag, matching Royale parser and
emitter behavior.
+- JSON Schema and schema compatibility policy. Implemented with a packaged
versioned schema and validator-backed tests.
-### PR 3: Build-tool integration
+### Slice 3: Build-tool integration
-- Add a `compile-codegraph` goal to `royale-maven-plugin`, modeled after
`CompileASDocMojo`.
-- Add Ant/tool registration and SDK launcher scripts.
-- Ensure both target configurations select the same dependency classifiers as
`CompileASDocMojo`.
+- `compile-codegraph` goal in `royale-maven-plugin`. Implemented with
dedicated graph configs and separate JS/SWF outputs.
+- Ant/tool registration and SDK launcher scripts. Implemented with a
`codegraph.jar` entry point and Unix/Windows launchers.
+- Target-specific dependency classifiers. Implemented to match
`CompileASDocMojo` without invoking the ASDoc compiler.
-### PR 4: `royale-asjs` integration
+### Slice 4: `royale-asjs` integration
This work happens in the sibling repository:
diff --git a/compiler-jx/build.xml b/compiler-jx/build.xml
index a1fd8149e..c80a397e2 100644
--- a/compiler-jx/build.xml
+++ b/compiler-jx/build.xml
@@ -55,6 +55,7 @@
<property name="compc.jar" value="${lib}/compc.jar" />
<property name="jsc.jar" value="${lib}/jsc.jar" />
<property name="asdoc.jar" value="${lib}/asdoc.jar" />
+ <property name="codegraph.jar" value="${lib}/codegraph.jar" />
<!-- env.BUILD_NUMBER is set by Jenkins CI -->
<condition property="build.number" value="nightly-${env.BUILD_NUMBER}">
@@ -166,6 +167,20 @@
</manifest>
</jar>
+ <echo message="Building ${codegraph.jar}" />
+ <jar file="${codegraph.jar}" basedir="${classes.dir}">
+ <include name="META-INF/LICENSE"/>
+ <include name="META-INF/NOTICE"/>
+ <manifest>
+ <attribute name="Sealed" value="${manifest.sealed}"/>
+ <attribute name="Implementation-Title"
value="${manifest.Implementation-Title}"/>
+ <attribute name="Implementation-Version"
value="${manifest.Implementation-Version}.${build.number}"/>
+ <attribute name="Implementation-Vendor"
value="${manifest.Implementation-Vendor}"/>
+ <attribute name="Main-Class"
value="org.apache.royale.compiler.clients.CODEGRAPH" />
+ <attribute name="Class-Path" value="jsc.jar" />
+ </manifest>
+ </jar>
+
<chmod dir="${basedir}/src/assembly" excludes="**/*.bat" perm="+x" />
</target>
@@ -196,6 +211,7 @@
<include name="mxmlc.jar" />
<include name="jsc.jar" />
<include name="asdoc.jar" />
+ <include name="codegraph.jar" />
</fileset>
</delete>
</target>
diff --git a/compiler-jx/pom.xml b/compiler-jx/pom.xml
index f084a58f4..fab8a5656 100644
--- a/compiler-jx/pom.xml
+++ b/compiler-jx/pom.xml
@@ -234,6 +234,18 @@
<version>4.13.2</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.networknt</groupId>
+ <artifactId>json-schema-validator</artifactId>
+ <version>1.0.87</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-nop</artifactId>
+ <version>2.0.7</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.apache.royale.compiler</groupId>
<artifactId>compiler-externc</artifactId>
diff --git a/compiler-jx/src/assembly/scripts/codegraph
b/compiler-jx/src/assembly/scripts/codegraph
new file mode 100755
index 000000000..e3db7b9c7
--- /dev/null
+++ b/compiler-jx/src/assembly/scripts/codegraph
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+################################################################################
+##
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements. See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+##
+################################################################################
+
+SCRIPT_HOME=`dirname "$0"`
+
+if [ "x${ROYALE_COMPILER_HOME}" = "x" ]
+then
+ ROYALE_COMPILER_HOME=${SCRIPT_HOME}/../../compiler
+fi
+
+echo Using Royale codebase: $ROYALE_COMPILER_HOME
+
+if [ "x${ROYALE_HOME}" = "x" ]
+then
+ ROYALE_HOME=${ROYALE_COMPILER_HOME}/generated/dist/sdk
+fi
+echo Using Royale SDK: $ROYALE_HOME
+
+case `uname` in
+ CYGWIN*)
+ OS="Windows"
+ ;;
+ *)
+ OS=Unix
+esac
+
+D32=''
+
+if [ $OS = "Windows" ]; then
+ ROYALE_COMPILER_HOME=`cygpath -m $ROYALE_COMPILER_HOME`
+ ROYALE_HOME=`cygpath -m $ROYALE_HOME`
+elif [ $OS = "Unix" ]; then
+ check64="`java -version 2>&1 | grep -i 64-Bit`"
+ isOSX="`uname | grep -i Darwin`"
+ javaVersion="`java -version 2>&1 | awk -F '[ ".]+' 'NR==1 {print $3 "."
$4}'`"
+
+ if [ "$isOSX" != "" -a "$HOSTTYPE" = "x86_64" -a "$check64" != "" -a
"$javaVersion" = "1.6" ]; then
+ D32='-d32'
+ fi
+fi
+
+VMARGS="-Xmx384m -Dsun.io.useCanonCaches=false "
+
+java $VMARGS $D32 $SETUP_SH_VMARGS -Droyalecompiler="$ROYALE_COMPILER_HOME"
-Droyalelib="$ROYALE_HOME/frameworks" -jar "$SCRIPT_HOME/../lib/codegraph.jar"
+royalelib="$ROYALE_HOME/frameworks" "$@"
\ No newline at end of file
diff --git a/compiler-jx/src/assembly/scripts/codegraph.bat
b/compiler-jx/src/assembly/scripts/codegraph.bat
new file mode 100644
index 000000000..cb080d54f
--- /dev/null
+++ b/compiler-jx/src/assembly/scripts/codegraph.bat
@@ -0,0 +1,24 @@
+@echo off
+
+rem
+rem Licensed to the Apache Software Foundation (ASF) under one or more
+rem contributor license agreements. See the NOTICE file distributed with
+rem this work for additional information regarding copyright ownership.
+rem The ASF licenses this file to You under the Apache License, Version 2.0
+rem (the "License"); you may not use this file except in compliance with
+rem the License. You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+rem
+
+if "x%ROYALE_COMPILER_HOME%"=="x" (set ROYALE_COMPILER_HOME=%~dp0..) else
echo Using Royale codebase: %ROYALE_COMPILER_HOME%
+
+if "x%ROYALE_HOME%"=="x" (set ROYALE_HOME=%~dp0..) else echo Using Royale SDK:
%ROYALE_HOME%
+
+@java -Dsun.io.useCanonCaches=false -Xms32m -Xmx512m
-Droyalecompiler="%ROYALE_COMPILER_HOME%"
-Droyalelib="%ROYALE_HOME%\frameworks" -jar
"%ROYALE_COMPILER_HOME%\lib\codegraph.jar" %*
\ No newline at end of file
diff --git
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java
index 3ccf2e40b..ace87b2c8 100644
---
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java
+++
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java
@@ -106,7 +106,10 @@ public class CODEGRAPH extends MXMLJSCRoyale
definitions.addAll(compilationUnit.getFileScopeRequest().get().getExternallyVisibleDefinitions());
}
- String module = FilenameUtils.getBaseName(config.getTargetFile());
+ String targetFile = config.getTargetFile();
+ String module = targetFile == null
+ ? FilenameUtils.getBaseName(getGraphOutputFile().getName())
+ : FilenameUtils.getBaseName(targetFile);
CodeGraphModel model = new
CodeGraphExporter(project).export(definitions, getGraphTarget(), module);
syntaxTrees.clear();
File outputFile = getGraphOutputFile();
@@ -161,7 +164,8 @@ public class CODEGRAPH extends MXMLJSCRoyale
File sourceFile = new
File(compilationUnit.getAbsoluteFilename()).getAbsoluteFile();
if (project.isFileOnSourcePath(sourceFile))
return true;
- if (sourceFile.equals(new
File(config.getTargetFile()).getAbsoluteFile()))
+ if (config.getTargetFile() != null
+ && sourceFile.equals(new
File(config.getTargetFile()).getAbsoluteFile()))
return true;
for (File includeSource : targetSettings.getIncludeSources())
{
@@ -189,9 +193,12 @@ public class CODEGRAPH extends MXMLJSCRoyale
Collection<File> includeSources = new ArrayList<File>();
for (String includeSource : config.getIncludeSources())
includeSources.add(new File(includeSource));
- File targetFile = new File(config.getTargetFile());
- if (!includeSources.contains(targetFile))
- includeSources.add(targetFile);
+ if (config.getTargetFile() != null)
+ {
+ File targetFile = new File(config.getTargetFile());
+ if (!includeSources.contains(targetFile))
+ includeSources.add(targetFile);
+ }
projectConfigurator.setIncludeSources(includeSources);
targetSettings =
projectConfigurator.getTargetSettings(getTargetType());
}
@@ -221,6 +228,8 @@ public class CODEGRAPH extends MXMLJSCRoyale
{
if (config.getOutput() != null)
return new File(config.getOutput());
+ if (config.getTargetFile() == null)
+ return new File("codegraph.json");
return new File(FilenameUtils.removeExtension(config.getTargetFile())
+ ".codegraph.json");
}
diff --git
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/RoyaleToolGroup.java
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/RoyaleToolGroup.java
index d2bf90f56..5aae7caeb 100644
---
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/RoyaleToolGroup.java
+++
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/RoyaleToolGroup.java
@@ -31,6 +31,7 @@ public class RoyaleToolGroup extends AbstractFlexToolGroup {
addFlexTool(new COMPJSC());
addFlexTool(new MXMLJSC());
addFlexTool(new ASDOCJSC(new MXMLRoyaleASDocDITABackend()));
+ addFlexTool(new CODEGRAPH());
addFlexTool(new EXTERNC());
addFlexTool(new PLAYERGLOBALC());
}
diff --git
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
index 4b89c380d..74a405a84 100644
---
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
+++
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
@@ -112,8 +112,8 @@ public final class CodeGraphExporter
String baseClassName =
classDefinition.getBaseClassAsDisplayString();
if (baseClass != null || (baseClassName != null &&
!baseClassName.isEmpty()))
symbol.setBaseType(createReference(baseClass, baseClassName));
- for (IInterfaceDefinition interfaceDefinition :
classDefinition.resolveImplementedInterfaces(project))
- symbol.addInterface(createReference(interfaceDefinition));
+ addInterfaces(symbol,
classDefinition.resolveImplementedInterfaces(project),
+
classDefinition.getImplementedInterfacesAsDisplayStrings());
IFunctionDefinition constructor = classDefinition.getConstructor();
if (constructor != null && !constructor.isImplicit())
symbol.addMember(exportFunction(constructor, definition));
@@ -121,8 +121,8 @@ public final class CodeGraphExporter
else
{
IInterfaceDefinition interfaceDefinition =
(IInterfaceDefinition)definition;
- for (IInterfaceDefinition extendedInterface :
interfaceDefinition.resolveExtendedInterfaces(project))
- symbol.addInterface(createReference(extendedInterface));
+ addInterfaces(symbol,
interfaceDefinition.resolveExtendedInterfaces(project),
+
interfaceDefinition.getExtendedInterfacesAsDisplayStrings());
}
for (IDefinition memberDefinition :
definition.getContainedScope().getAllLocalDefinitions())
@@ -141,6 +141,19 @@ public final class CodeGraphExporter
return symbol;
}
+ private void addInterfaces(CodeGraphSymbol symbol, IInterfaceDefinition[]
definitions, String[] displayNames)
+ {
+ int count = Math.max(definitions.length, displayNames.length);
+ for (int i = 0; i < count; i++)
+ {
+ IInterfaceDefinition definition = i < definitions.length ?
definitions[i] : null;
+ String displayName = i < displayNames.length ? displayNames[i] :
null;
+ CodeGraphReference reference = createReference(definition,
displayName);
+ if (reference != null)
+ symbol.addInterface(reference);
+ }
+ }
+
private CodeGraphSymbol exportFunction(IFunctionDefinition definition,
ITypeDefinition declaringType)
{
String kind;
diff --git
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
index 0dcbda427..c52ee156c 100644
---
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
+++
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
@@ -376,6 +376,9 @@ public final class CodeGraphWriter
writer.write(": ");
if (value == null)
writer.write("null");
+ else if ((value instanceof Double && !Double.isFinite((Double)value))
+ || (value instanceof Float && !Float.isFinite((Float)value)))
+ writeString(writer, value.toString());
else if (value instanceof Number || value instanceof Boolean)
writer.write(value.toString());
else
diff --git
a/compiler-jx/src/main/resources/org/apache/royale/compiler/internal/codegen/graph/codegraph-1.0.schema.json
b/compiler-jx/src/main/resources/org/apache/royale/compiler/internal/codegen/graph/codegraph-1.0.schema.json
new file mode 100644
index 000000000..1b1b9dba7
--- /dev/null
+++
b/compiler-jx/src/main/resources/org/apache/royale/compiler/internal/codegen/graph/codegraph-1.0.schema.json
@@ -0,0 +1,158 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://royale.apache.org/schemas/codegraph-1.0.schema.json",
+ "title": "Apache Royale Code Graph",
+ "description": "A deterministic graph of public ActionScript API
definitions.",
+ "type": "object",
+ "required": ["schemaVersion", "target", "module", "symbols",
"externalSymbols"],
+ "additionalProperties": false,
+ "properties": {
+ "schemaVersion": { "const": "1.0" },
+ "target": { "enum": ["js", "swf"] },
+ "module": { "type": ["string", "null"] },
+ "symbols": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/symbol" }
+ },
+ "externalSymbols": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/symbol" }
+ }
+ },
+ "$defs": {
+ "value": {
+ "type": ["string", "number", "boolean", "null"]
+ },
+ "reference": {
+ "type": "object",
+ "required": ["id", "qualifiedName", "external", "unresolved"],
+ "additionalProperties": false,
+ "properties": {
+ "id": { "type": "string" },
+ "qualifiedName": { "type": "string" },
+ "external": { "type": "boolean" },
+ "unresolved": { "type": "boolean" }
+ }
+ },
+ "modifiers": {
+ "type": "object",
+ "required": ["static", "final", "dynamic", "override", "abstract",
"native"],
+ "additionalProperties": false,
+ "properties": {
+ "static": { "type": "boolean" },
+ "final": { "type": "boolean" },
+ "dynamic": { "type": "boolean" },
+ "override": { "type": "boolean" },
+ "abstract": { "type": "boolean" },
+ "native": { "type": "boolean" }
+ }
+ },
+ "asdocTag": {
+ "type": "object",
+ "required": ["name", "description"],
+ "additionalProperties": false,
+ "properties": {
+ "name": { "type": "string" },
+ "description": { "type": ["string", "null"] }
+ }
+ },
+ "asdoc": {
+ "type": "object",
+ "required": ["description", "tags"],
+ "additionalProperties": false,
+ "properties": {
+ "description": { "type": ["string", "null"] },
+ "tags": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/asdocTag" }
+ }
+ }
+ },
+ "metadataAttribute": {
+ "type": "object",
+ "required": ["key", "value"],
+ "additionalProperties": false,
+ "properties": {
+ "key": { "type": ["string", "null"] },
+ "value": { "type": ["string", "null"] }
+ }
+ },
+ "metadata": {
+ "type": "object",
+ "required": ["name", "attributes"],
+ "additionalProperties": false,
+ "properties": {
+ "name": { "type": "string" },
+ "attributes": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/metadataAttribute" }
+ },
+ "references": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/reference" }
+ },
+ "asdoc": { "$ref": "#/$defs/asdoc" }
+ }
+ },
+ "parameter": {
+ "type": "object",
+ "required": ["name", "type", "optional", "rest", "defaultValue"],
+ "additionalProperties": false,
+ "properties": {
+ "name": { "type": "string" },
+ "type": {
+ "oneOf": [
+ { "$ref": "#/$defs/reference" },
+ { "type": "null" }
+ ]
+ },
+ "optional": { "type": "boolean" },
+ "rest": { "type": "boolean" },
+ "defaultValue": { "$ref": "#/$defs/value" }
+ }
+ },
+ "symbol": {
+ "type": "object",
+ "required": ["id", "qualifiedName", "baseName", "package", "kind"],
+ "additionalProperties": false,
+ "properties": {
+ "id": { "type": "string" },
+ "qualifiedName": { "type": "string" },
+ "baseName": { "type": "string" },
+ "package": { "type": "string" },
+ "kind": {
+ "enum": ["class", "interface", "constructor", "method", "getter",
"setter", "field", "constant", "function", "variable"]
+ },
+ "external": { "type": "boolean" },
+ "source": { "type": "string" },
+ "origin": { "type": "string" },
+ "visibility": { "type": "string" },
+ "modifiers": { "$ref": "#/$defs/modifiers" },
+ "initialValue": { "$ref": "#/$defs/value" },
+ "declaringType": { "$ref": "#/$defs/reference" },
+ "type": { "$ref": "#/$defs/reference" },
+ "returnType": { "$ref": "#/$defs/reference" },
+ "baseType": { "$ref": "#/$defs/reference" },
+ "overrides": { "$ref": "#/$defs/reference" },
+ "implements": { "$ref": "#/$defs/reference" },
+ "asdoc": { "$ref": "#/$defs/asdoc" },
+ "interfaces": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/reference" }
+ },
+ "parameters": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/parameter" }
+ },
+ "metadata": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/metadata" }
+ },
+ "members": {
+ "type": "array",
+ "items": { "$ref": "#/$defs/symbol" }
+ }
+ }
+ }
+ }
+}
diff --git
a/compiler-jx/src/test/java/org/apache/royale/compiler/clients/TestCODEGRAPH.java
b/compiler-jx/src/test/java/org/apache/royale/compiler/clients/TestCODEGRAPH.java
index f669d83eb..16ab2b22c 100644
---
a/compiler-jx/src/test/java/org/apache/royale/compiler/clients/TestCODEGRAPH.java
+++
b/compiler-jx/src/test/java/org/apache/royale/compiler/clients/TestCODEGRAPH.java
@@ -28,6 +28,9 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import org.apache.flex.tools.FlexTool;
+import org.apache.flex.tools.FlexToolGroup;
+import org.apache.flex.tools.FlexToolRegistry;
import org.apache.commons.io.FileUtils;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.UnknownTypeProblem;
@@ -56,6 +59,16 @@ public class TestCODEGRAPH
FileUtils.deleteQuietly(outputFile);
}
+ @Test
+ public void testRegisteredAsRoyaleFlexTool()
+ {
+ FlexToolRegistry registry = new FlexToolRegistry();
+ FlexToolGroup toolGroup = registry.getToolGroup("Royale");
+ FlexTool tool = toolGroup.getFlexTool("codegraph");
+
+ assertTrue(tool instanceof CODEGRAPH);
+ }
+
@Test
public void testCompilerErrorsPreventOutputByDefault()
{
@@ -75,6 +88,7 @@ public class TestCODEGRAPH
assertTrue(problems.toString(),
containsProblem(UnknownTypeProblem.class));
String output = FileUtils.readFileToString(outputFile, "UTF-8");
assertTrue(output, output.contains("\"qualifiedName\":
\"MissingType\""));
+ assertTrue(output, output.contains("\"qualifiedName\":
\"MissingInterface\""));
assertTrue(output, output.contains("\"unresolved\": true"));
}
@@ -136,6 +150,34 @@ public class TestCODEGRAPH
assertTrue(output,
output.contains("as3://codegraph/conditional/IncludedOnly"));
}
+ @Test
+ public void testIncludeSourcesDirectoryDoesNotRequireTargetFile() throws
IOException
+ {
+ File sourceDirectory = testAdapter.getUnitTestBaseDir();
+ File includeDirectory = new File(sourceDirectory,
"codegraph/conditional");
+ outputFile = new File(testAdapter.getTempDir(),
"codegraph/IncludedDirectory.json");
+
+ int exitCode = compile(null, sourceDirectory, includeDirectory, false,
false);
+ assertEquals(problems.toString(), 0, exitCode);
+ String output = FileUtils.readFileToString(outputFile, "UTF-8");
+ assertTrue(output, output.contains("\"module\":
\"IncludedDirectory\""));
+ assertTrue(output,
output.contains("as3://codegraph/conditional/ConditionalGraph"));
+ assertTrue(output,
output.contains("as3://codegraph/conditional/IncludedOnly"));
+ }
+
+ @Test
+ public void testIncludeSourcesDirectoryDoesNotRequireSourcePath() throws
IOException
+ {
+ File includeDirectory = new File(testAdapter.getUnitTestBaseDir(),
"codegraph/conditional");
+ outputFile = new File(testAdapter.getTempDir(),
"codegraph/IncludedDirectory.json");
+
+ int exitCode = compile(null, null, includeDirectory, false, false);
+ assertEquals(problems.toString(), 0, exitCode);
+ String output = FileUtils.readFileToString(outputFile, "UTF-8");
+ assertTrue(output,
output.contains("as3://codegraph/conditional/ConditionalGraph"));
+ assertTrue(output,
output.contains("as3://codegraph/conditional/IncludedOnly"));
+ }
+
private int compile(boolean createTargetWithErrors)
{
File sourceFile = new File(testAdapter.getUnitTestBaseDir(),
"codegraph/InvalidCodeGraph.as");
@@ -179,7 +221,8 @@ public class TestCODEGRAPH
arguments.add("-define=COMPILE::JS," + !swf);
arguments.add("-define+=COMPILE::SWF," + swf);
}
- arguments.add(sourceFile.getPath());
+ if (sourceFile != null)
+ arguments.add(sourceFile.getPath());
problems = new ArrayList<ICompilerProblem>();
return new CODEGRAPH().mainNoExit(arguments.toArray(new
String[arguments.size()]), problems, false);
diff --git
a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
index 4ee729549..9d10c9f49 100644
---
a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
+++
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
@@ -23,8 +23,16 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
+import java.io.InputStream;
import java.io.StringWriter;
+import java.util.Set;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SpecVersion;
+import com.networknt.schema.ValidationMessage;
import org.junit.Test;
public class TestCodeGraphWriter
@@ -42,6 +50,8 @@ public class TestCodeGraphWriter
StringWriter output = new StringWriter();
new CodeGraphWriter().write(model, output);
+ assertConformsToSchema(output.toString());
+
assertEquals("{\n"
+ " \"schemaVersion\": \"1.0\",\n"
+ " \"target\": \"js\",\n"
@@ -96,6 +106,7 @@ public class TestCodeGraphWriter
writer.write(model, secondOutput);
assertEquals(firstOutput.toString(), secondOutput.toString());
+ assertConformsToSchema(firstOutput.toString());
assertTrue(firstOutput.toString().contains("\"baseType\": {"));
assertTrue(firstOutput.toString().contains("\"members\": ["));
assertTrue(firstOutput.toString().contains("\"parameters\": ["));
@@ -106,4 +117,70 @@ public class TestCodeGraphWriter
assertTrue(firstOutput.toString().contains("\"key\": null"));
assertTrue(firstOutput.toString().contains("\"external\": true"));
}
+
+ @Test
+ public void testGoldenDocumentConformsToSchema() throws IOException
+ {
+ InputStream documentInput =
getClass().getResourceAsStream("/codegraph/golden/GraphRoot.json");
+ assertTrue("Golden code graph resource is missing", documentInput !=
null);
+ try
+ {
+ assertConformsToSchema(new ObjectMapper().readTree(documentInput));
+ }
+ finally
+ {
+ documentInput.close();
+ }
+ }
+
+ @Test
+ public void testNonFiniteValuesAreValidJSONStrings() throws IOException
+ {
+ CodeGraphModel model = new CodeGraphModel("js", "non-finite-values");
+ CodeGraphSymbol symbol = new CodeGraphSymbol("as3://example/Values",
"example.Values", "Values",
+ "example", "class");
+ CodeGraphSymbol nan = new CodeGraphSymbol("as3://example/Values#nan",
"example.Values.nan", "nan",
+ "example", "constant");
+ nan.setInitialValue(Double.NaN);
+ CodeGraphSymbol positiveInfinity = new
CodeGraphSymbol("as3://example/Values#positiveInfinity",
+ "example.Values.positiveInfinity", "positiveInfinity",
"example", "constant");
+ positiveInfinity.setInitialValue(Double.POSITIVE_INFINITY);
+ CodeGraphSymbol negativeInfinity = new
CodeGraphSymbol("as3://example/Values#negativeInfinity",
+ "example.Values.negativeInfinity", "negativeInfinity",
"example", "constant");
+ negativeInfinity.setInitialValue(Double.NEGATIVE_INFINITY);
+ symbol.addMember(nan);
+ symbol.addMember(positiveInfinity);
+ symbol.addMember(negativeInfinity);
+ model.addSymbol(symbol);
+
+ StringWriter output = new StringWriter();
+ new CodeGraphWriter().write(model, output);
+
+ assertConformsToSchema(output.toString());
+ assertTrue(output.toString().contains("\"initialValue\": \"NaN\""));
+ assertTrue(output.toString().contains("\"initialValue\":
\"Infinity\""));
+ assertTrue(output.toString().contains("\"initialValue\":
\"-Infinity\""));
+ }
+
+ private void assertConformsToSchema(String document) throws IOException
+ {
+ assertConformsToSchema(new ObjectMapper().readTree(document));
+ }
+
+ private void assertConformsToSchema(JsonNode document) throws IOException
+ {
+ InputStream schemaInput =
getClass().getResourceAsStream("codegraph-1.0.schema.json");
+ assertTrue("Code graph schema resource is missing", schemaInput !=
null);
+ try
+ {
+ JsonSchemaFactory factory =
JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
+ JsonSchema schema = factory.getSchema(schemaInput);
+ Set<ValidationMessage> errors = schema.validate(document);
+ assertTrue(errors.toString(), errors.isEmpty());
+ }
+ finally
+ {
+ schemaInput.close();
+ }
+ }
}
\ No newline at end of file
diff --git a/compiler-jx/src/test/resources/codegraph/InvalidCodeGraph.as
b/compiler-jx/src/test/resources/codegraph/InvalidCodeGraph.as
index f7ad08b7e..9d577256a 100644
--- a/compiler-jx/src/test/resources/codegraph/InvalidCodeGraph.as
+++ b/compiler-jx/src/test/resources/codegraph/InvalidCodeGraph.as
@@ -1,6 +1,6 @@
package codegraph
{
- public class InvalidCodeGraph
+ public class InvalidCodeGraph implements MissingInterface
{
public var unresolved:MissingType;
diff --git
a/royale-maven-plugin/src/main/java/org/apache/royale/maven/CompileCodeGraphMojo.java
b/royale-maven-plugin/src/main/java/org/apache/royale/maven/CompileCodeGraphMojo.java
new file mode 100644
index 000000000..bddb112ac
--- /dev/null
+++
b/royale-maven-plugin/src/main/java/org/apache/royale/maven/CompileCodeGraphMojo.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.royale.maven;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Goal which exports the public API graph for the project.
+ */
+@Mojo(name="compile-codegraph", defaultPhase = LifecyclePhase.SITE)
+public class CompileCodeGraphMojo
+ extends BaseMojo
+{
+
+ @Parameter(defaultValue = "codegraph")
+ private String codeGraphDirectoryName;
+
+ @Parameter(defaultValue = "${project.artifactId}.json")
+ private String outputFileName;
+
+ @Parameter(defaultValue = "false")
+ private boolean skipCodeGraph;
+
+ @Parameter(defaultValue = "false")
+ private boolean skipAS;
+
+ private ThreadLocal<Type> type = new ThreadLocal<Type>();
+
+ @Override
+ protected String getToolGroupName() {
+ return "Royale";
+ }
+
+ @Override
+ protected String getFlexTool() {
+ return "codegraph";
+ }
+
+ @Override
+ protected String getConfigFileName() throws MojoExecutionException {
+ if(type.get() == null) {
+ throw new MojoExecutionException("type not set");
+ }
+ switch (type.get()) {
+ case SWF:
+ return "compile-codegraph-swf-config.xml";
+ case JS:
+ return "compile-codegraph-js-config.xml";
+ }
+ return null;
+ }
+
+ @Override
+ protected File getOutput() throws MojoExecutionException {
+ if(type.get() == null) {
+ throw new MojoExecutionException("type not set");
+ }
+ return new File(new File(new File(outputDirectory,
codeGraphDirectoryName),
+ type.get() == Type.SWF ? "swf" : "js"), outputFileName);
+ }
+
+ @Override
+ protected boolean skip() {
+ return skipCodeGraph;
+ }
+
+ @Override
+ public void execute() throws MojoExecutionException {
+ try {
+ if (!skipAS)
+ {
+ getLog().info("Generating SWF code graph");
+ type.set(Type.SWF);
+ super.execute();
+ getLog().info("Finished");
+ }
+
+ getLog().info("Generating JS code graph");
+ type.set(Type.JS);
+ super.execute();
+ getLog().info("Finished");
+ } finally {
+ type.remove();
+ }
+ }
+
+ @Override
+ protected List<Define> getDefines() throws MojoExecutionException {
+ List<Define> defines = super.getDefines();
+ if(type.get() == null) {
+ throw new MojoExecutionException("type not set");
+ }
+ switch (type.get()) {
+ case SWF:
+ defines.add(new Define("COMPILE::JS", "false"));
+ defines.add(new Define("COMPILE::SWF", "true"));
+ break;
+ case JS:
+ defines.add(new Define("COMPILE::JS", "true"));
+ defines.add(new Define("COMPILE::SWF", "false"));
+ break;
+ }
+ return defines;
+ }
+
+ @Override
+ protected boolean includeLibrary(Artifact library) {
+ String scope = library.getScope();
+ if ("test".equalsIgnoreCase(scope)) {
+ return false;
+ }
+ switch (type.get()) {
+ case SWF: {
+ String classifier = library.getClassifier();
+ return "swf".equalsIgnoreCase(classifier) ||
+ ((classifier == null) &&
"runtime".equalsIgnoreCase(scope));
+ }
+ case JS: {
+ String classifier = library.getClassifier();
+ return "typedefs".equalsIgnoreCase(classifier) ||
+ "js".equalsIgnoreCase(classifier);
+ }
+ }
+ return false;
+ }
+
+ private enum Type {
+ SWF,
+ JS
+ }
+
+}
\ No newline at end of file
diff --git
a/royale-maven-plugin/src/main/resources/config/compile-codegraph-js-config.xml
b/royale-maven-plugin/src/main/resources/config/compile-codegraph-js-config.xml
new file mode 100644
index 000000000..1cd47231f
--- /dev/null
+++
b/royale-maven-plugin/src/main/resources/config/compile-codegraph-js-config.xml
@@ -0,0 +1,90 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<royale-config>
+
+ <compiler>
+ <debug>$debug</debug>
+
+ <library-path>
+#foreach($artifact in $libraries)
<path-element>$artifact.file</path-element>
+#end
+ </library-path>
+
+ <external-library-path>
+#foreach($artifact in $externalLibraries)
<path-element>$artifact.file</path-element>
+#end
+ </external-library-path>
+
+ <source-path>
+#foreach($sourcePath in $sourcePaths)
<path-element>$sourcePath</path-element>
+#end
+ </source-path>
+
+ <namespaces>
+#foreach($namespace in $namespaces) <namespace>
+ <uri>$namespace.uri</uri>
+ <manifest>$namespace.manifest</manifest>
+ </namespace>
+#end
+#if($languageNamespace)
+ <namespace>
+ <uri>$languageNamespace.uri</uri>
+ <manifest>$languageNamespace.manifest</manifest>
+ </namespace>
+#end
+ </namespaces>
+
+ <keep-as3-metadata>
+#foreach($metadata in $keepAs3Metadata) <name>$metadata</name>
+#end
+ </keep-as3-metadata>
+
+
<allow-subclass-overrides>$allowSubclassOverrides</allow-subclass-overrides>
+ <show-deprecation-warnings>false</show-deprecation-warnings>
+
+#foreach($define in $defines) <define>
+ <name>$define.name</name>
+ <value>$define.value</value>
+ </define>
+#end
+ </compiler>
+
+ <include-sources>
+#foreach($sourcePath in $sourcePaths)
<path-element>$sourcePath</path-element>
+#end
+ </include-sources>
+
+#if($includeClasses)
+ <include-classes>
+#foreach($includeClass in $includeClasses) <class>$includeClass</class>
+#end
+ </include-classes>
+#end
+
+#if($namespaceUris)
+ <include-namespaces>
+#foreach($namespaceUri in $namespaceUris) <uri>$namespaceUri</uri>
+#end
+ </include-namespaces>
+#end
+
+ <keep-asdoc>true</keep-asdoc>
+ <output>${output}</output>
+
+</royale-config>
\ No newline at end of file
diff --git
a/royale-maven-plugin/src/main/resources/config/compile-codegraph-swf-config.xml
b/royale-maven-plugin/src/main/resources/config/compile-codegraph-swf-config.xml
new file mode 100644
index 000000000..ad5610444
--- /dev/null
+++
b/royale-maven-plugin/src/main/resources/config/compile-codegraph-swf-config.xml
@@ -0,0 +1,91 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<royale-config>
+
+ <compiler>
+ <debug>$debug</debug>
+
+ <library-path>
+#foreach($artifact in $libraries)
<path-element>$artifact.file</path-element>
+#end
+ </library-path>
+
+ <external-library-path>
+#foreach($artifact in $externalLibraries)
<path-element>$artifact.file</path-element>
+#end
+ </external-library-path>
+
+ <source-path>
+#foreach($sourcePath in $sourcePaths)
<path-element>$sourcePath</path-element>
+#end
+ </source-path>
+
+ <namespaces>
+#foreach($namespace in $namespaces) <namespace>
+ <uri>$namespace.uri</uri>
+ <manifest>$namespace.manifest</manifest>
+ </namespace>
+#end
+#if($languageNamespace)
+ <namespace>
+ <uri>$languageNamespace.uri</uri>
+ <manifest>$languageNamespace.manifest</manifest>
+ </namespace>
+#end
+ </namespaces>
+
+ <keep-as3-metadata>
+#foreach($metadata in $keepAs3Metadata) <name>$metadata</name>
+#end
+ </keep-as3-metadata>
+
+
<allow-subclass-overrides>$allowSubclassOverrides</allow-subclass-overrides>
+ <show-deprecation-warnings>false</show-deprecation-warnings>
+
+#foreach($define in $defines) <define>
+ <name>$define.name</name>
+ <value>$define.value</value>
+ </define>
+#end
+ </compiler>
+
+ <include-sources>
+#foreach($sourcePath in $sourcePaths)
<path-element>$sourcePath</path-element>
+#end
+ </include-sources>
+
+#if($includeClasses)
+ <include-classes>
+#foreach($includeClass in $includeClasses) <class>$includeClass</class>
+#end
+ </include-classes>
+#end
+
+#if($namespaceUris)
+ <include-namespaces>
+#foreach($namespaceUri in $namespaceUris) <uri>$namespaceUri</uri>
+#end
+ </include-namespaces>
+#end
+
+ <target-player>${targetPlayer}</target-player>
+ <keep-asdoc>true</keep-asdoc>
+ <output>${output}</output>
+
+</royale-config>
\ No newline at end of file