From 292ab23796a166785f507a5cd221445f83f7641f Mon Sep 17 00:00:00 2001 From: lisa kanbur Date: Mon, 27 Nov 2023 17:07:08 -0500 Subject: [PATCH 1/3] Dataspace changes from pure --- .../src/main/resources/ideLightConfig.json | 2 +- .../pom.xml | 205 ++++++ ...LineageAnalysisCodeRepositoryProvider.java | 30 + ...lesystem.repository.CodeRepositoryProvider | 65 ++ .../core_analytics_quality.definition.json | 14 + .../associationChecks.pure | 31 + .../badAssociationReport.pure | 54 ++ .../core_analytics_quality/checksEngine.pure | 369 ++++++++++ .../core_analytics_quality/classChecks.pure | 84 +++ .../enumerationChecks.pure | 65 ++ .../functionChecks.pure | 636 ++++++++++++++++++ .../propertyChecks.pure | 312 +++++++++ .../tests/associationChecksTest.pure | 38 ++ .../tests/classChecksTest.pure | 146 ++++ .../tests/enumerationChecksTest.pure | 63 ++ .../tests/functionChecksTest.pure | 223 ++++++ .../tests/propertyChecksTest.pure | 40 ++ .../tests/testQuality.pure | 265 ++++++++ .../pom.xml | 22 + legend-engine-xts-analytics/pom.xml | 1 + pom.xml | 5 + 21 files changed, 2669 insertions(+), 1 deletion(-) create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/pom.xml create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/java/org/finos/legend/pure/code/core/CoreExternalLineageAnalysisCodeRepositoryProvider.java create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/META-INF/services/org.finos.legend.pure.m3.serialization.filesystem.repository.CodeRepositoryProvider create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality.definition.json create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/associationChecks.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/badAssociationReport.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/checksEngine.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/classChecks.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/enumerationChecks.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/functionChecks.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/propertyChecks.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/associationChecksTest.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/classChecksTest.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/enumerationChecksTest.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/functionChecksTest.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/propertyChecksTest.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/testQuality.pure create mode 100644 legend-engine-xts-analytics/legend-engine-xts-analytics-quality/pom.xml diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light/src/main/resources/ideLightConfig.json b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light/src/main/resources/ideLightConfig.json index 4e3d9a0b71d..52acbe688d9 100644 --- a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light/src/main/resources/ideLightConfig.json +++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light/src/main/resources/ideLightConfig.json @@ -21,7 +21,7 @@ "connector": { "maxRequestHeaderSize": "32KiB", "type": "http", - "port": 9200 + "port": 9010 }, "requestLog": { "appenders": [ diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/pom.xml b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/pom.xml new file mode 100644 index 00000000000..1f92e7e880b --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/pom.xml @@ -0,0 +1,205 @@ + + + + legend-engine-xts-analytics-quality + org.finos.legend.engine + 4.35.5-SNAPSHOT + + 4.0.0 + + legend-engine-xt-analytics-quality-pure + Legend Engine - XT - Analytics - Quality - PAR/JAVA + jar + + + 11 + 11 + + + + + + org.finos.legend.pure + legend-pure-maven-generation-par + + src/main/resources + ${legend.pure.version} + + platform + core + core_relational + core_analytics_quality + + + ${project.basedir}/src/main/resources/core_analytics_quality.definition.json + + + + + compile + + build-pure-jar + + + + + + org.finos.legend.pure + legend-pure-m2-dsl-mapping-grammar + ${legend.pure.version} + + + org.finos.legend.pure + legend-pure-m2-dsl-diagram-grammar + ${legend.pure.version} + + + org.finos.legend.pure + legend-pure-m2-dsl-graph-grammar + ${legend.pure.version} + + + org.finos.legend.pure + legend-pure-m2-dsl-path-grammar + ${legend.pure.version} + + + org.finos.legend.pure + legend-pure-m2-store-relational-grammar + ${legend.pure.version} + + + + org.finos.legend.engine + legend-engine-pure-code-compiled-core + ${project.version} + + + org.finos.legend.engine + legend-engine-xt-relationalStore-pure + ${project.version} + + + + + org.finos.legend.pure + legend-pure-maven-generation-java + + + compile + + build-pure-compiled-jar + + + true + modular + true + + core_analytics_quality + + + + + + + org.finos.legend.pure + legend-pure-m2-dsl-mapping-grammar + ${legend.pure.version} + + + org.finos.legend.pure + legend-pure-m2-dsl-diagram-grammar + ${legend.pure.version} + + + org.finos.legend.pure + legend-pure-m2-dsl-graph-grammar + ${legend.pure.version} + + + org.finos.legend.pure + legend-pure-m2-dsl-path-grammar + ${legend.pure.version} + + + org.finos.legend.pure + legend-pure-m2-dsl-path-grammar + ${legend.pure.version} + + + org.finos.legend.pure + legend-pure-m2-store-relational-grammar + ${legend.pure.version} + + + + org.finos.legend.engine + legend-engine-pure-code-compiled-core + ${project.version} + + + org.finos.legend.engine + legend-engine-xt-relationalStore-pure + ${project.version} + + + + + + + + + + org.finos.legend.pure + legend-pure-m4 + + + org.finos.legend.pure + legend-pure-m3-core + + + + org.finos.legend.pure + legend-pure-runtime-java-engine-compiled + + + org.finos.legend.pure + legend-pure-m2-store-relational-pure + + + + + org.finos.legend.engine + legend-engine-pure-code-compiled-core + + + org.finos.legend.engine + legend-engine-pure-code-compiled-functions + + + org.finos.legend.engine + legend-engine-pure-platform-java + + + org.finos.legend.engine + legend-engine-pure-platform-functions-java + + + + + org.eclipse.collections + eclipse-collections + + + org.eclipse.collections + eclipse-collections-api + + + + + + + + \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/java/org/finos/legend/pure/code/core/CoreExternalLineageAnalysisCodeRepositoryProvider.java b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/java/org/finos/legend/pure/code/core/CoreExternalLineageAnalysisCodeRepositoryProvider.java new file mode 100644 index 00000000000..06fcb194e3b --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/java/org/finos/legend/pure/code/core/CoreExternalLineageAnalysisCodeRepositoryProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright 2022 Goldman Sachs + * + * Licensed 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.finos.legend.pure.code.core; + +import org.finos.legend.pure.m3.serialization.filesystem.repository.CodeRepository; +import org.finos.legend.pure.m3.serialization.filesystem.repository.CodeRepositoryProvider; +import org.finos.legend.pure.m3.serialization.filesystem.repository.GenericCodeRepository; + +public class CoreExternalLineageAnalysisCodeRepositoryProvider implements CodeRepositoryProvider +{ + @Override + public CodeRepository repository() + { + return GenericCodeRepository.build("core_analytics_quality.definition.json"); + } +} diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/META-INF/services/org.finos.legend.pure.m3.serialization.filesystem.repository.CodeRepositoryProvider b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/META-INF/services/org.finos.legend.pure.m3.serialization.filesystem.repository.CodeRepositoryProvider new file mode 100644 index 00000000000..91ac69dcfc0 --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/META-INF/services/org.finos.legend.pure.m3.serialization.filesystem.repository.CodeRepositoryProvider @@ -0,0 +1,65 @@ +# +# Copyright 2022 Goldman Sachs +# +# Licensed 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. +# + +# +# Copyright 2022 Goldman Sachs +# +# Licensed 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. +# + +# +# Copyright 2022 Goldman Sachs +# +# Licensed 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. +# + +# +# Copyright 2022 Goldman Sachs +# +# Licensed 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. +# + +org.finos.legend.pure.code.core.CoreExternalLineageAnalysisCodeRepositoryProvider \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality.definition.json b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality.definition.json new file mode 100644 index 00000000000..eec8a9ba4a3 --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality.definition.json @@ -0,0 +1,14 @@ +{ + "name": "core_analytics_quality", + "pattern": "(meta::analytics::quality|meta::pure::quality)(::.*)?", + "dependencies": [ + "platform", + "platform_functions", + "platform_dsl_mapping", + "platform_store_relational", + "platform_functions_json", + "core_functions", + "core", + "core_relational" + ] +} \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/associationChecks.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/associationChecks.pure new file mode 100644 index 00000000000..a1fed8f7937 --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/associationChecks.pure @@ -0,0 +1,31 @@ +import meta::analytics::quality::model::*; +import meta::analytics::quality::*; +import meta::analytics::quality::model::domain::*; + +function meta::analytics::quality::model::domain::associationRules():Rule[*] +{ + [ + associationNameShouldStartWithUpperCase_Association_1__CheckResult_MANY_ + ]->map(rule|createRule($rule)->cast(@Rule)); + +} + +function <> + { rule.rule = 'Invalid Association Name', + rule.description ='Camel case must be used Association name and should be upper camel case, with an underscore between both sides of the join.', + rule.severity = 'High', + rule.category = 'Modelling', + doc.doc = 'Returns true if Association name is valid'} +meta::analytics::quality::model::domain::associationNameShouldStartWithUpperCase(a:Association[1]):CheckResult[*] +{ + + let containsUnderScore = $a.name->meta::pure::functions::string::contains('_'); + let tokens = if($containsUnderScore,| meta::pure::functions::string::split($a.name->toOne(), '_'), |$a.name->toOne()); + + let passed = size($tokens) == 2 && + $tokens->at(0)->meta::pure::functions::string::substring(0,1) ->isUpperCase() && + $tokens->at(1)->meta::pure::functions::string::substring(0,1) ->isUpperCase(); + let message ='Check name of association ' + $a.name->toOne() ; + + ^CheckResult(isValid=$passed, message=$message); +} \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/badAssociationReport.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/badAssociationReport.pure new file mode 100644 index 00000000000..8d885a1bd64 --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/badAssociationReport.pure @@ -0,0 +1,54 @@ +import meta::relational::mapping::*; + + +///TEMPORARY BAD ASSOCIATIONS REPORT - DELETE THIS ONCE IT IS ALL CLEANED UP + +function {service.url='/badAssociations', service.contentType='text/csv', service.contentDisposition='attachment;filename=badAssociations.csv'} +meta::analytics::quality::model::domain::badAssociationsReport() : String[1] +{ + + let allAssoc = Association.all(); + + let badAsscs = $allAssoc->map(a | + let rawType1 = $a.properties->at(0).genericType.rawType; + let rawType2 = $a.properties->at(1).genericType.rawType; + if ($rawType1->isEmpty() || $rawType2->isEmpty(), + | [], + | let source1 = $rawType1->toOne()->sourceInformation().source->toOne(); + let source2 = $rawType2->toOne()->sourceInformation().source->toOne(); + let repo1 = $source1->split('/')->first()->toOne(); + let repo2 = $source2->split('/')->first()->toOne(); + + if($repo1 == $repo2 || ($repo1->startsWith('model') && $repo2->startsWith('model')), | [], | ^meta::analytics::quality::model::domain::badAssociations::BadAssociation(association=$a, + nonModelRepo=if($repo1 == 'model', | $repo2, |$repo1)); + ); + ); + ); + + let vals = $badAsscs->map(a | $a.association->elementToPath())->sort(); + + $vals->makeString('\n'); +} + + +Class meta::analytics::quality::model::domain::badAssociations::BadAssociation +{ + association:Association[1]; + //name:String[1]; + nonModelRepo:String[1]; + + isUsedInMapping() + { + !$this.modelProperty().referenceUsages.owner->evaluateAndDeactivate()->filter(e | $e->instanceOf(RelationalPropertyMapping))->isEmpty() + }:Boolean[1]; + + modelProperty() + { + let rawType1 = $this.association.properties->at(0).genericType.rawType->toOne(); + let rawType2 = $this.association.properties->at(1).genericType.rawType->toOne(); + + if ($rawType1->sourceInformation().source->startsWith('/model'), | $this.association.properties->at(0), + | $this.association.properties->at(1)); + + }:Property[1]; +} diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/checksEngine.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/checksEngine.pure new file mode 100644 index 00000000000..cc24c780113 --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/checksEngine.pure @@ -0,0 +1,369 @@ +import meta::analytics::quality::model::*; +import meta::analytics::quality::model::domain::*; +import meta::relational::tests::*; +import meta::pure::runtime::*; + + +Enum meta::analytics::quality::model::Severity +{ + High, Medium, Low +} + +Enum meta::analytics::quality::model::Category +{ + Modelling, Correctness, Quality, Testing +} + +Class meta::analytics::quality::model::Rule +{ + id : String[1]; + func: FunctionDefinition<{T[1]->Any[*]}>[1]; + severity : Severity[1]; + category : Category[1]; + description: String[1]; + hasSkipTestsFlag : Boolean[1]; + +} + +Profile meta::analytics::quality::model::rule +{ + stereotypes: [skipTests]; + tags: [rule, severity, category, description, ignore]; +} + +Class meta::analytics::quality::model::CheckResult +{ + isValid: Boolean[1]; + message: String[1]; +} + +Class meta::analytics::quality::model::ViolationInstance +{ + source : T[1]; + rule : Rule[1]; + sourceInfo() { $this.source->sourceInformation()}: SourceInformation[0..1] ; + detail : + CheckResult[1]; +} + +Class meta::analytics::quality::model::Rankings +{ + severity() { [pair(Severity.High, 1), pair(Severity.Medium, 2),pair(Severity.Low, 3)]}: Pair[*]; + severity(severity :Severity[1]) { $this.severity()->filter(p|$p.first == $severity)->toOne().second} : Integer[1]; + +} + +function <> meta::analytics::quality::model::domain::apply(rule:Rule[1],pe:AbstractProperty[1]):Any[*] +{ + if($rule->skipInTests($pe), |[], |$rule.func->eval($pe)) +} + +function {doc.doc = 'Run quality checks for class properties'} +meta::analytics::quality::model::domain::runClassPropertyQualityChecks(cl:Class[1],rules:Rule>[*]):ViolationInstance[*] +{ + let properties = $cl->allProperties(); + + $properties->map(p:AbstractProperty[1] | $rules->map(rule:Rule>[1] | $rule->apply($p)->cast(@CheckResult)->violationHandler($p, $rule))); +} + +function <> meta::analytics::quality::model::domain::skipInTests(rule:Rule[1],pe:Any[1]):Boolean[1] +{ + if($rule.hasSkipTestsFlag , + | + let pName = $pe->match([p:PackageableElement[1]|$p->fullPackageName('::'), p:AbstractProperty[1]|$p->fullPackageName('::')]); + let si = $pe->sourceInformation(); + $pName->contains('::tests::') || $pName->contains('::test::')|| $si.source->contains('/test/') || $si.source->contains('/tests/');, + | false + ); +} + +function {doc.doc = 'Handles model checks violations'} +//TODO: there is a bug with generics so ViolationInstance instead of ViolationInstance +meta::analytics::quality::model::domain::violationHandler(detail:CheckResult[*],element: Any[1],rule: Rule[1]):ViolationInstance[*] +{ + $detail->filter(r | !$r.isValid)->map(r | ^ViolationInstance(source=$element, rule=$rule, detail=$r)); +} + +function meta::analytics::quality::model::domain::createRule(ruleFunctionB: FunctionDefinition<{T[1]->Any[*]}>[1]):Rule[1] +{ + let ruleFunction = $ruleFunctionB->cast(@AnnotatedElement); + let tags = $ruleFunction.taggedValues; + + let rule = $ruleFunction->value4Tag('rule', rule).value->toOne(); + let description = $ruleFunction->value4Tag('description', rule).value->toOne(); + let severity = Severity->extractEnumValue($tags->filter(t | $t.tag == rule->tag('severity'))->map(t | $t.value)->toOne()->toString()); + let category = Category->extractEnumValue($tags->filter(t | $t.tag == rule->tag('category'))->map(t | $t.value)->toOne()->toString()); + let skipTestsTag = $ruleFunction->hasStereotype('skipTests',meta::analytics::quality::model::rule); + ^Rule(id=$rule,func=$ruleFunctionB,severity=$severity,category=$category,description=$description,hasSkipTestsFlag=$skipTestsTag); +} + +function {doc.doc='Run quality checks for all elements in a package'} +meta::analytics::quality::model::domain::runQualityChecks(elements:PackageableElement[*]):ViolationInstance[*] +{ + let functionRules = functionRules(); + let enumerationRules = enumerationRules(); + let associationRules = associationRules(); + let classRules = classRules(); + let propertyRules = propertyRules(); + + let allElements = $elements->filter(e | $e->instanceOf(Package))->cast(@Package)->map(p | $p->getAllPackageElements(true))->concatenate($elements)->removeDuplicates(); + + let funcViolations = $allElements->filter(x|$x->instanceOf(FunctionDefinition))->cast(@FunctionDefinition)->runQualityChecksForFunctions($functionRules); + let classes = $allElements->filter(x|$x->instanceOf(Class))->cast(@Class); + let associations = $allElements->filter(x|$x->instanceOf(Association)); + let classViolations = $classes->runQualityChecksForClass($classRules, $propertyRules)->concatenate($classes.qualifiedProperties->runQualityChecksForFunctions($functionRules)); + let enumerationViolations = $allElements->filter(x|$x->instanceOf(Enumeration))->cast(@Enumeration)->runQualityChecksForEnumerations($enumerationRules); + let associationViolations = $allElements->filter(x|$x->instanceOf(Association))->cast(@Association)->runQualityChecksForAssociations($associationRules); + + $funcViolations->concatenate($classViolations)->concatenate($enumerationViolations)->concatenate($associationViolations); +} + +function {doc.doc = 'Run Quality checks for functions'} +meta::analytics::quality::model::domain::runQualityChecksForFunctions(fns:FunctionDefinition[*], rules : Rule>>[*]):ViolationInstance[*] +{ + if($fns->isEmpty() + ,|[] + ,|$rules->map(rule| + let ruleFunction=$rule.func->cast(@FunctionDefinition<{List>[1]->Pair[*]}>);//todo this is a hack for compile mode + + let filtersFns = $fns->filter(f|!$rule->skipInTests($f)); + + let results = $ruleFunction->eval(^List>(values=$filtersFns)); + + + $results->map(p| + []->concatenate($p.second)->violationHandler($p.first,$rule); + ); + ); + ); + +} + +function {doc.doc = 'Run quality checks for classes, their properties and abstract functions'} +meta::analytics::quality::model::domain::runQualityChecksForClass(cls:Class[*], classRules:Rule>[*], propRules:Rule>[*]):ViolationInstance[*] +{ + + $cls->map(cl| $classRules->map(rule:Rule>[1] | $rule->apply($cl)->cast(@CheckResult)->violationHandler($cl, $rule)) + ->concatenate($cl->meta::analytics::quality::model::domain::runClassPropertyQualityChecks($propRules))); +} + +function {doc.doc = 'Run quality checks for enums'} +meta::analytics::quality::model::domain::runQualityChecksForEnumerations(e:Enumeration[*], enumerationRules:Rule>[*]):ViolationInstance[*] +{ + $e->map(e | $enumerationRules->map(rule:Rule>[1] | $rule->apply($e)->cast(@CheckResult)->violationHandler($e, $rule))); +} + +function {doc.doc = 'Run quality checks for associations'} +meta::analytics::quality::model::domain::runQualityChecksForAssociations(e:Association[*], associationRules:Rule[*]):ViolationInstance[*] +{ + $e->map(e | $associationRules->map(rule:Rule[1] | $rule->apply($e)->cast(@CheckResult)->violationHandler($e, $rule))); +} + +function <> meta::analytics::quality::model::domain::apply(rule:Rule[1],pe:PackageableElement[1]):Any[*] +{ + if($rule->skipInTests($pe), |[], |$rule.func->eval($pe)) +} + +function meta::analytics::quality::model::domain::runRules(pkg:PackageableElement[*]) : ViolationInstance[*] +{ + let severity = newMap(^Rankings().severity); + + $pkg->removeDuplicates() + ->runQualityChecks() + ->removeDuplicates() + ->cast(@ViolationInstance) + ->filter(bug|!$bug.detail.isValid) + ->filterInstancesToIgnore() + ->sortBy(b | $severity->get($b.rule.severity)->toOne()); + +} + +function meta::analytics::quality::model::domain::filterInstancesToIgnore(i:ViolationInstance[*]) : ViolationInstance[*] +{ + + $i->filter(v| let ignoreTag = $v.source->match([e:ElementWithTaggedValues[1]| $e->value4Tag('ignore', rule).value, + fe:FunctionExpression[1]| $fe.usageContext->match( + [es:ExpressionSequenceValueSpecificationContext[1]| if($es.functionDefinition->instanceOf(AnnotatedElement),|$es.functionDefinition->cast(@AnnotatedElement)->value4Tag('ignore', rule).value, |[]);, + a: ValueSpecificationContext[1]|'' + ]);, + a:Any[1]| println($a);'';]); + if( $ignoreTag->isEmpty() + ,| true + ,| let values = $ignoreTag->toOne()->split(','); + !$v.rule.id->in($values); + ); + + ); +} + +function meta::analytics::quality::model::domain::runChecksAsHtml(pkgs:PackageableElement[*],elements:String[*],service:Boolean[1]):String[1] +{ + let bugs = meta::analytics::quality::model::domain::runRules($pkgs); + let bugDetailsTable = [ + '
', + '', + '', + '', + '', + '', + '', + '', + '' + ]->concatenate( + $bugs->map({bug| + let pe = $bug.source->findPackagableElement(); + let peName = $bug.source->match([p:PackageableElement[1]| $p->fullPackageName('::'),a:Any[1]|$pe->fullPackageName('::') ]) ; + [ + '', + (''), + (''), + (''), + (''), + if($service + ,|('') + ,|('') + ), + '' + ]; + }) + )->concatenate([ + '
SeverityCategoryRuleMessageElement
' + $bug.rule.severity->makeString() + '' + $bug.rule.category->makeString() + '' + $bug.rule.description->makeString() + '' + $bug.detail.message->makeString() + '' + $peName + '' + 'toOne().source + '\",' + $bug.sourceInfo->toOne().startLine->toString() + ',' + $bug.sourceInfo->toOne().startColumn->toString() + ',false); return false;\'>' + + $peName+ ''+ '
', + '
' + ]); + + let packagesInfo = [ + '
', + '

', + 'Inspected elements:
' + ] + ->concatenate($elements->map(e | $e + '
')->sort()->joinStrings('')) + ->concatenate( + ['

']) + ; + + let rulesInfo = [ + '
', + '', + '', + '', + '', + '', + '', + '', + '' + ] + ->concatenate(classRules()->map(r | $r->createRuleHTMLRow('Classes',$service))) + ->concatenate(propertyRules()->map(r | $r->createRuleHTMLRow('Properties',$service))) + ->concatenate(functionRules()->map(r | $r->createRuleHTMLRow('Functions',$service))) + ->concatenate(enumerationRules()->map(r | $r->createRuleHTMLRow('Enumerations',$service))) + ->concatenate(associationRules()->map(r | $r->createRuleHTMLRow('Associations',$service))) + ->concatenate([ + '
RuleSeverityCategoryDescriptionRule code
', + '
' + ]); + + let stats = '
'+ $bugs->meta::analytics::quality::model::domain::buildStatsTable('Rule')+'
'; + + let scripts = if($service,| + ''+ + ''+ + '' + ,|''); + + let page = [ + '', + 'PURE Quality Checks', + '', + '', + $scripts, + '', + '', + '', + '
', + ''] + ->concatenate($stats) + ->concatenate($packagesInfo) + ->concatenate($bugDetailsTable) + ->concatenate($rulesInfo) + ->concatenate + ([ + '', + '', + '' + ]) ->joinStrings('\n'); + $page; +} + +function <> meta::analytics::quality::model::domain::createRuleHTMLRow(r:Rule[1],type:String[1],service:Boolean[1]):String[1] +{ + + let pe = $r.func; + let si= $pe->sourceInformation()->toOne(); + + ''+$type +''+$r.severity->toString() + '' + $r.category->toString() + '' + $r.description +'' + + + if($service + ,|('' + $pe->cast(@PackageableElement)->fullPackageName('::') + '') + ,|('' + 'toString() + ',' + $si.startColumn->toString() + ',false); return false;\'>' + + $pe->cast(@PackageableElement)->fullPackageName('::') + ''+ '') + ); + +} + +function <> meta::analytics::quality::model::domain::buildStatsTable(items : ViolationInstance[*], name : String[1]) : String[1] +{ + let severity = newMap(^Rankings().severity); + + let pairs = $items->map(e| ^Pair,Integer>(first=$e.rule,second=1)); + + let keys = $pairs.first->distinct()->cast(@Rule); + + let stats = $keys->map(k|let p=^Pair(first=$severity->get($k.severity)->toOne(),second=$pairs->filter(p|$p.first == $k).second->sum()); + ^Pair,Pair>(first=$k, second = $p);) + ->sort({a,b| $a.second.first->compare($b.second.first) + (-1* $a.second.second->compare($b.second.second)) }); // sort by severity then count + + + let rows =$stats + ->map({p| + let percent = (($p.second.second / $items->size()) * 100); + [ + '', + ('' + $p.first.id->makeString() + ''), + ('' + $p.first.severity->makeString() + ''), + ('' + $p.second.second->makeString() + ''), + ('' + if ($percent > 0.1, | format('%.2f', [$percent]), | 'n/a') + '%'), + '' + ]; + })->cast(@String); + + let statsTable = [ + ''] + ->concatenate(['']) + ->concatenate([(''), + '', + '', + '', + '']) + ->concatenate($rows) + ->concatenate(['
Total',$items->size()->toString(),'Violation Instances
' + $name + 'SeverityCount%
']); + + $statsTable->joinStrings(''); +} + +function {service.url='/quality/reports/{element}', service.contentType='text/html'} +meta::analytics::quality::model::domain::findBugsReports(element : String[1]) : String[1] +{ + let pe = $element->pathToElement(); + [$pe]->meta::analytics::quality::model::domain::runChecksAsHtml($pe->fullPackageName('::'),true); +} \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/classChecks.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/classChecks.pure new file mode 100644 index 00000000000..ab1caba18c2 --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/classChecks.pure @@ -0,0 +1,84 @@ +import meta::analytics::quality::model::*; +import meta::analytics::quality::*; +import meta::analytics::quality::model::domain::*; + + +function meta::analytics::quality::model::domain::classRules():Rule>[*] +{ + [ + meta::analytics::quality::model::domain::classNameShouldStartWithUpperCase_Class_1__CheckResult_MANY_, + meta::analytics::quality::model::domain::entityNameShouldNotStartWithPackageName_Class_1__CheckResult_MANY_, + meta::analytics::quality::model::domain::classHasAtLeastOneMandatoryProperty_Class_1__CheckResult_MANY_ , + meta::analytics::quality::model::domain::allEntitiesAndPropertiesShouldHaveALongDescription_Class_1__CheckResult_MANY_ + ]->map(rule|createRule($rule)->cast(@Rule>)); + +} + +function { rule.rule = 'Invalid Class Names', + rule.description ='Class name should start with Upper case', + rule.severity = 'Medium', + rule.category = 'Modelling', + doc.doc = 'Returns true if the class name starts with upper case letter'} +meta::analytics::quality::model::domain::classNameShouldStartWithUpperCase(cl:Class[1]):CheckResult[*] +{ + let issues = if(!$cl.name->toOne()->meta::pure::functions::string::substring(0,1)->isUpperCase(), + | 'should start with upper case', + | []) + ->concatenate(if ($cl.name->contains('_'), + | 'should not contain \'_\'', + | [] + )); + + let message = if($issues->isEmpty(), + | 'Class name (' + $cl.name->toOne() + ') matches required standards', + | 'Class name (' + $cl.name->toOne() + ') does not match required standards: ' + $issues->joinStrings(';') + ); + + ^CheckResult(isValid=$issues->isEmpty(), message=$message); +} + +function <> + { rule.rule = 'Class has no mandatory properties', + rule.description ='Class should have at least one mandatory property', + rule.severity = 'High', + rule.category = 'Modelling', + doc.doc = 'Returns true if the class has a mandatory property'} +meta::analytics::quality::model::domain::classHasAtLeastOneMandatoryProperty(cl:Class[1]):CheckResult[*] +{ + let passed = $cl.properties->filter(p | $p.multiplicity->isToOne())->isNotEmpty(); + + let message = if($passed, | 'Class has at least one mandatory property', | 'Class should have at least one mandatory property'); + + ^CheckResult(isValid=$passed, message=$message); +} + +function <> + { rule.rule = 'Invalid Entity Name', + rule.description ='Entity name must not start with the package name.', + rule.severity = 'High', + rule.category = 'Modelling', + doc.doc = 'Returns true if Entity name is valid'} +meta::analytics::quality::model::domain::entityNameShouldNotStartWithPackageName(cl:Class[1]):CheckResult[*] +{ + let passed = !($cl.name->toOne()->toLower()->startsWith($cl.package.name->toOne()->toLower())); + + let message ='Check name of class and package ' + $cl.name->toOne() ; + + ^CheckResult(isValid=$passed, message=$message); +} + +function <> + { rule.rule = 'Documentation not provided for entity and/or its properties', + rule.description ='All entities and properties must have a long description.', + rule.severity = 'High', + rule.category = 'Modelling', + doc.doc = 'Returns true if long description is provided for entites and its properties'} +meta::analytics::quality::model::domain::allEntitiesAndPropertiesShouldHaveALongDescription(cl:Class[1]):CheckResult[*] +{ + let passed = ($cl.taggedValues->size() != 0 && $cl.taggedValues->filter(t | $t.tag.profile == doc)->size() != 0) + && $cl.properties->size() == $cl.properties->map(p|$p.taggedValues)-> filter(t | $t.tag.profile == doc)->size(); + + let message ='Provide documentation for class and its properties ' + $cl.name->toOne() ; + + ^CheckResult(isValid=$passed, message=$message); +} \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/enumerationChecks.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/enumerationChecks.pure new file mode 100644 index 00000000000..cf9030294eb --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/enumerationChecks.pure @@ -0,0 +1,65 @@ +import meta::analytics::quality::*; +import meta::analytics::quality::model::*; +import meta::analytics::quality::model::domain::*; + +function meta::analytics::quality::model::domain::enumerationRules():Rule>[*] +{ + [ + meta::analytics::quality::model::domain::enumerationName_Enumeration_1__CheckResult_MANY_, + meta::analytics::quality::model::domain::enumerationValue_Enumeration_1__CheckResult_MANY_ + ]->map(rule|createRule($rule)->cast(@Rule>)); + +} + +function { rule.rule = 'Invalid Enumeration Names', + rule.description ='Enum name should start with Upper case, not contain underscores or the word Enum', + rule.severity = 'Medium', + rule.category = 'Modelling', + doc.doc = 'Returns true if the enum name meets the guidelines'} +meta::analytics::quality::model::domain::enumerationName(e:Enumeration[1]):CheckResult[*] +{ + let issues = if(!$e->enumName()->toOne()->meta::pure::functions::string::substring(0,1)->isUpperCase(), + | 'should start with upper case', + | []) + ->concatenate(if ($e->enumName()->contains('_'), + | 'should not contain \'_\'', + | [] + )) + ->concatenate(if ($e->enumName()->contains('Enum'), + | 'should not contain \'Enum\'', + | [] + )); + + let message = if($issues->isEmpty(), + | 'Enumeration name (' + $e->enumName() + ') matches required standards', + | 'Enumeration name (' + $e->enumName() + ') does not match required standards: ' + $issues->joinStrings(';') + ); + + ^CheckResult(isValid=$issues->isEmpty(), message=$message); +} + +function { rule.rule = 'Invalid Enumeration Values', + rule.description ='Enum value name should should start with, but not be all, Upper case nor contain underscore', + rule.severity = 'Low', + rule.category = 'Modelling', + doc.doc = 'Returns true if the enum name meets the guidelines'} +meta::analytics::quality::model::domain::enumerationValue(e:Enumeration[1]):CheckResult[*] +{ + let issues = if($e->enumName()->toOne()->meta::pure::functions::string::isUpperCase() + || true->in($e->enumValues()->map(v|$v->cast(@Enum).name->meta::pure::functions::string::isUpperCase())), + | 'should not be all upper case', + | []) + ->concatenate(if (true->in($e->enumValues()->map(v|$v->cast(@Enum).name->contains('_'))), + | 'should not contain \'_\'', + | [])) + ->concatenate(if (false->in($e->enumValues()->map(v|$v->cast(@Enum).name->toOne()->meta::pure::functions::string::substring(0,1)->isUpperCase())), + | 'should start with upper case', + | [])); + + let message = if($issues->isEmpty(), + | 'Enumeration name (' + $e->enumName() + ') matches required standards', + | 'Enumeration name (' + $e->enumName() + ') does not match required standards: ' + $issues->joinStrings(';') + ); + + ^CheckResult(isValid=$issues->isEmpty(), message=$message); +} \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/functionChecks.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/functionChecks.pure new file mode 100644 index 00000000000..aabd6f182df --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/functionChecks.pure @@ -0,0 +1,636 @@ +import meta::analytics::quality::*; +import meta::analytics::quality::model::*; +import meta::pure::functions::collection::*; +import meta::pure::functions::meta::applications::*; +import meta::pure::metamodel::serialization::grammar::*; +import meta::analytics::quality::model::domain::*; + +function meta::analytics::quality::model::domain::functionRules():Rule>>[*] +{ + + [ + meta::analytics::quality::model::domain::validEqualityComparisons_List_1__Pair_MANY_, + meta::analytics::quality::model::domain::invalidContainsComparisons_List_1__Pair_MANY_, + meta::analytics::quality::model::domain::badInstanceOfChecks_List_1__Pair_MANY_, + meta::analytics::quality::model::domain::invalidMatchUsages_List_1__Pair_MANY_, + meta::analytics::quality::model::domain::findUnnecessaryComparisonsToTrue_List_1__Pair_MANY_, + meta::analytics::quality::model::domain::findInvalidCastBugs_List_1__Pair_MANY_, + meta::analytics::quality::model::domain::findUnusedPrivateProtectedFunctionBugs_List_1__Pair_MANY_, + meta::analytics::quality::model::domain::findUnnecessaryIfBugs_List_1__Pair_MANY_, + meta::analytics::quality::model::domain::findUnnecessaryIfBugs2_List_1__Pair_MANY_, + // apps::pure::quality::findUnnecessaryToOneBugs_List_1__Pair_MANY_, enable when compile mode is fixed + meta::analytics::quality::model::domain::findUnnecessaryCasts_List_1__Pair_MANY_, + meta::analytics::quality::model::domain::findUnnecessaryLetFunctionsCheck_List_1__Pair_MANY_, + meta::analytics::quality::model::domain::findPotentiallyExpensiveAssertions_List_1__Pair_MANY_ + ]->map(rule|createRule($rule)->cast(@Rule>>)); + + +} + +function meta::analytics::quality::model::domain::getUsages(srcFns:List>[1],fns:Function[*]):FunctionExpression[*] +{ + $srcFns.values->map(f| + $f->evaluateAndDeactivate()->meta::analytics::quality::model::domain::applicationsMatchLocal({vs : FunctionExpression[1] | $vs.func->in($fns)}); + ); +} + +function meta::analytics::quality::model::domain::applicationsMatchLocal(expr : FunctionDefinition[1], matchFunc:Function<{FunctionExpression[1]->Boolean[1]}>[1]):FunctionExpression[*] +{ + let fes = $expr->evaluateAndDeactivate().expressionSequence->meta::analytics::quality::model::domain::getLocalFunctionExpressions(); + $fes->filter(fe|$matchFunc->eval($fe)); +} + +function <> meta::analytics::quality::model::domain::getLocalFunctionExpressions(vs : ValueSpecification[*]) : FunctionExpression[*] +{ + $vs->map(x| + $x->match([ + sfe:SimpleFunctionExpression[1]| + $sfe->concatenate($sfe.parametersValues->meta::analytics::quality::model::domain::getLocalFunctionExpressions()), + v : VariableExpression[1]|[], + iv : InstanceValue[1]|$iv.values->map(v| + $v->match([ + subVS:ValueSpecification[1]|$subVS->meta::analytics::quality::model::domain::getLocalFunctionExpressions(), + lambda:LambdaFunction[1]|$lambda.expressionSequence->meta::analytics::quality::model::domain::getLocalFunctionExpressions(), + a:Any[*]| [] + ]) + ) + ])); +} + +function {rule.rule = 'Invalid Contains', + rule.description ='Check for contains / containsAll / containsAny checks that will always result in false due to type miss matches (e.g. [\'abc\']->contains(123))', + rule.severity = 'High', + rule.category = 'Correctness'} +meta::analytics::quality::model::domain::invalidContainsComparisons(fns:List>[1]) : Pair[*] +{ + + + meta::analytics::quality::model::domain::findInvalidContainsComparisons($fns) + ->map(ie| + let t1 = $ie->meta::analytics::quality::model::domain::resolveParameterType(0); + let t2 = $ie->meta::analytics::quality::model::domain::resolveParameterType(1); + let message = ('Possible invalid ' + $ie.functionName->makeString() + ' check (type miss match, ' + $t1->elementToPath() + ' vs ' + $t2->elementToPath() + ')'); + let result = ^CheckResult(isValid=false,message=$message); + pair($ie,$result); + ); +} + + +function {rule.rule = 'Invalid Equality', + rule.description ='Check for equality checks that will always result in false due to type miss matches (e.g. \'abc\' == 123)', + rule.severity = 'High', + rule.category = 'Correctness'} +meta::analytics::quality::model::domain::validEqualityComparisons(fns:List>[1]) : Pair[*] +{ + + let applications = [equal_Any_MANY__Any_MANY__Boolean_1_, + is_Any_1__Any_1__Boolean_1_, + eq_Any_1__Any_1__Boolean_1_]; + + let usages = $fns->meta::analytics::quality::model::domain::getUsages($applications); + + $usages->evaluateAndDeactivate() + ->map(ie| + let t1 = $ie->meta::analytics::quality::model::domain::resolveParameterType(0); + let t2 = $ie->meta::analytics::quality::model::domain::resolveParameterType(1); + + let valid = meta::analytics::quality::model::domain::equalityCompatibleTypes($t1, $t2); + + let message = if($valid, + |'Valid ' + $ie.functionName->toOne() + ' check (' + $t1->elementToPath() + ' vs ' + $t2->elementToPath() + ')', + |'Possible invalid ' + $ie.functionName->toOne() + ' check (type mismatch, ' + $t1->elementToPath() + ' vs ' + $t2->elementToPath() + ')'); + let result = ^CheckResult(isValid=$valid, message=$message); + pair($ie, $result); + ); + +} + + +function <> meta::analytics::quality::model::domain::equalityCompatibleTypes(t1 : Type[1], t2 : Type[1]) : Boolean[1] +{ + castCompatibleTypes($t1, $t2) || ($t1->subTypeOf(Number) && $t2->subTypeOf(Number)); +} + + + +// Nested filters +function meta::analytics::quality::model::domain::findAllNestedFilterExpressions():FunctionExpression[*] +{ + meta::pure::functions::collection::filter_T_MANY__Function_1__T_MANY_.applications + ->evaluateAndDeactivate() + ->filter(fe | let arg1 = $fe.parametersValues->evaluateAndDeactivate()->at(0); + $arg1->instanceOf(FunctionExpression) && + is($arg1->cast(@FunctionExpression).func, + meta::pure::functions::collection::filter_T_MANY__Function_1__T_MANY_);) +} + + +function {rule.rule = 'Invalid Instance Of', + rule.description ='Check for Match functions that will always fail (e.g. \'abc\'->match([ i: Integer[1]|$i))', + rule.severity = 'High', + rule.category = 'Correctness'} +meta::analytics::quality::model::domain::badInstanceOfChecks(fns:List>[1]) : Pair[*] +{ + meta::analytics::quality::model::domain::findBadInstanceOfChecks($fns) + ->map(ie| + let t1 = $ie->meta::analytics::quality::model::domain::resolveParameterType(0); + let t2 = $ie.parametersValues->at(1)->meta::analytics::quality::model::domain::resolveValueType(); + let message = ('Invalid instanceOf check, always ' + meta::analytics::quality::model::domain::castNotNecessary($t1, $t2)->makeString() + ' (' + $t1->elementToPath() + ' vs ' + $t2->elementToPath() + ')'); + let result = ^CheckResult(isValid=false,message=$message); + pair($ie,$result); + ); +} + +function {rule.rule = 'Invalid Match', + rule.description ='Check for invalid instanceOf checks (e.g. \'hello\'->instanceOf(Float) or \'hello\'->instanceOf(String))', + rule.severity = 'High', + rule.category = 'Correctness'} +meta::analytics::quality::model::domain::invalidMatchUsages(fns:List>[1]) : Pair[*] +{ + meta::analytics::quality::model::domain::findInvalidMatchUsage($fns) + ->map(ie| + let t1 = $ie->meta::analytics::quality::model::domain::resolveParameterType(0); + let message = ('Possible invalid Match usage, no type matches for ' + $t1->elementToPath() + ')'); + let result = ^CheckResult(isValid=false,message=$message); + pair($ie,$result); + ); +} + +function {rule.rule = 'Bad Size', + rule.description ='Check that code uses $collection->isEmpty() / isNotEmpty() instead of $collection->size() == 0 / size() > 0 / size != 0', + rule.severity = 'Low', + rule.category = 'Quality'} +meta::analytics::quality::model::domain::badSizeComparisons(fns:List>[1]) : Pair[*] +{ + meta::analytics::quality::model::domain::findBadSizeComparisons($fns) + ->map(ie| + let message = ('Use isEmpty() / isNotEmpty() check, rather than size() ' + $ie.functionName->makeString() + ' 0'); + let result = ^CheckResult(isValid=false,message=$message); + pair($ie,$result); + ); +} + + +function {rule.rule = 'Unnecessary Comparison To True', + rule.description = 'Check for unnecessary comparison to true (e.g. if(myBooleanFunc() == true, .., ...))', + rule.severity = 'Medium', + rule.category = 'Quality'} +meta::analytics::quality::model::domain::findUnnecessaryComparisonsToTrue(fns:List>[1]) : Pair[*] +{ + meta::analytics::quality::model::domain::unnecessaryTrueComparisons($fns) + ->map(ie| + let message = ('Avoid unnecessary comparison to true for boolean values'); + let result = ^CheckResult(isValid=false,message=$message); + pair($ie,$result); + ); +} + +function {rule.rule = 'Invalid Cast', + rule.description = 'Check for invalid cast operations (e.g. \'hello\'->cast(@Float))', + rule.severity = 'High', + rule.category = 'Correctness'} +meta::analytics::quality::model::domain::findInvalidCastBugs(fns:List>[1]) : Pair[*] +{ + meta::analytics::quality::model::domain::findInvalidCasts($fns) + ->map(ie| + let t1 = $ie->meta::analytics::quality::model::domain::resolveParameterType(0); + let t2 = $ie.parametersValues->at(1)->meta::analytics::quality::model::domain::resolveValueType(); + let message = ('Possible invalid cast (from ' + $t1->elementToPath() + ' to ' + $t2->elementToPath() + ')'); + let result = ^CheckResult(isValid=false,message=$message); + pair($ie,$result); + ); +} + +function {rule.rule = 'Unused Private Protected', + rule.description = 'Check for unused private or protected functions (they could be removed)', + rule.severity = 'Low', + rule.category = 'Quality'} +meta::analytics::quality::model::domain::findUnusedPrivateProtectedFunctionBugs(fns:List>[1]) : Pair,CheckResult>[*] +{ + $fns.values->map(f| + if(meta::analytics::quality::model::domain::isPrivateProtectedFunctionUnused($f) + ,| let message = ('Unused private/protected function ' + $f.functionName->toOne()); + let result = ^CheckResult(isValid=false,message=$message); + [pair($f,$result)]; + ,|[]) + ); +} + +function meta::analytics::quality::model::domain::isPrivateProtectedFunctionUnused(f:Function[1]) :Boolean[1] +{ + $f->isFunctionUncalled() && + ($f->instanceOf(AnnotatedElement) && ($f->cast(@AnnotatedElement)->hasStereotype('private', meta::pure::profiles::access) || $f->cast(@AnnotatedElement)->hasStereotype('protected', meta::pure::profiles::access))); + +} + +function {doc.doc = 'Find if defined functions is not called anywhere.'} +meta::analytics::quality::model::domain::isFunctionUncalled(f:Function[1]):Boolean[1] +{ + !$f->isTestElement() && !$f->isFunctionReferenced() +} + +function <> meta::analytics::quality::model::domain::isFunctionReferenced(func:Function[1]):Boolean[1] +{ + !$func.applications->evaluateAndDeactivate()->isEmpty() || !$func.referenceUsages->isEmpty() +} + +function {rule.rule = 'Unnecessary If Condition', + rule.description = 'Check for unnecessary if statements, with constant condition (e.g. if(true, |abc, |def)))', + rule.severity = 'Medium', + rule.category = 'Quality'} +meta::analytics::quality::model::domain::findUnnecessaryIfBugs(fns:List>[1]) : Pair[*] +{ + meta::analytics::quality::model::domain::findUnnecessaryIfs($fns) + ->map(f| let message = ('If statement with constant expression'); + let result = ^CheckResult(isValid=false,message=$message); + pair($f,$result); + ) + +} + +function {rule.rule = 'Unnecessary If Return', + rule.description = 'Check for unnecessary if statements, with boolean result (e.g. if(2 > 1, |true, |false)))', + rule.severity = 'Medium', + rule.category = 'Quality'} +meta::analytics::quality::model::domain::findUnnecessaryIfBugs2(fns:List>[1]) : Pair[*] +{ + meta::analytics::quality::model::domain::findUnnecessaryIfs2($fns) + ->map(f|let message = ('If statement with true/false returns'); + let result = ^CheckResult(isValid=false,message=$message); + pair($f,$result); + ) + +} + +function {rule.rule = 'Unnecessary To One', + rule.description = 'Check for unnecessary toOne() operations (e.g. \'hello\'->toOne())', + rule.severity = 'Medium', + rule.category = 'Quality'} +meta::analytics::quality::model::domain::findUnnecessaryToOneBugs(fns:List>[1]) : Pair[*] +{ + meta::analytics::quality::model::domain::findUnnecessaryToOneFEs($fns) + ->map(ie| + let message = ('Avoid unnecessary toOne() on item that is already [1..1]'); + let result = ^CheckResult(isValid=false,message=$message); + pair($ie,$result); + ); +} + +function {rule.rule = 'Unnecessary Map Property', + rule.description = 'Check for unnecessary map to property usage (e.g. [pair(1,2), pair(3,4)]->map(p|$p.first))', + rule.severity = 'Medium', + rule.category = 'Quality'} +meta::analytics::quality::model::domain::findUnnecessaryMapToPropertyBugs(fns:List>[1]) : Pair[*] +{ + meta::analytics::quality::model::domain::findUnnecessaryMapToProperty($fns) + ->map(ie|let message = ('Use .property syntax directly against the collection rather than mapping to extract single property'); + let result = ^CheckResult(isValid=false,message=$message); + pair($ie,$result); + ); +} + + + + +function meta::analytics::quality::model::domain::findBadSizeComparisons(fns:List>[1]) : FunctionExpression[*] +{ + let applications = [equal_Any_MANY__Any_MANY__Boolean_1_,greaterThan_Number_1__Number_1__Boolean_1_]; + + + let usages = $fns->getUsages($applications); + + + let invalidApplications = $usages->evaluateAndDeactivate()->filter({i| + let p1 = $i.parametersValues->at(0); + let p2 = $i.parametersValues->at(1); + + $p1->instanceOf(SimpleFunctionExpression) && $p1->cast(@SimpleFunctionExpression).func == size_Any_MANY__Integer_1_ + && $p2->instanceOf(InstanceValue) && $p2->cast(@InstanceValue).values == [0]; + }); +} + +function meta::analytics::quality::model::domain::findInvalidContainsComparisons(fns:List>[1]) : FunctionExpression[*] +{ + let applications = [contains_Any_MANY__Any_1__Boolean_1_, + containsAny_Any_MANY__Any_MANY__Boolean_1_, + containsAll_Any_MANY__Any_MANY__Boolean_1_]; + + let usages = $fns->getUsages($applications); + + $usages->evaluateAndDeactivate() + ->filter({i| + let t1 = $i->resolveParameterType(0); + let t2 = $i.parametersValues->at(1)->resolveValueType(); + + let valid = equalityCompatibleTypes($t1, $t2); + + !$valid; + }); +} + +function meta::analytics::quality::model::domain::findInvalidMatchUsage(fns:List>[1]) : FunctionExpression[*] +{ + + let applications = [match_Any_MANY__Function_$1_MANY$__T_m_]; + + let usages = $fns->getUsages($applications); + + let invalidApplications = $usages->evaluateAndDeactivate()->filter({i| + + let t1 = $i->resolveParameterType(0); + let lambdasSpec = $i.parametersValues->at(1); + + //TODO: Handle the case where the match handler values are passed in by a variable + //TODO: Handle the case where the match handlers are functions (not lambdas) + + if($lambdasSpec->instanceOf(InstanceValue) == false + || !$lambdasSpec->cast(@InstanceValue).values->forAll(v|$v->instanceOf(LambdaFunction)), + | true, + | + let lambdas = $lambdasSpec->cast(@InstanceValue).values->cast(@LambdaFunction); + + let matchLambdaParamTypes = $lambdas->map(l|$l->functionType().parameters->toOne()); + + let valid = $matchLambdaParamTypes->fold({pt,r|if ($r, |$r, {| + $t1->castCompatibleTypes($pt.genericType.rawType->toOne()); + })}, false); + + !$valid; + ); + }); + + $invalidApplications; +} + +function meta::analytics::quality::model::domain::findInvalidCasts(fns:List>[1]) : FunctionExpression[*] +{ + let applications = [cast_Any_m__T_1__T_m_]; + + let usages = $fns->getUsages($applications); + + let invalidApplications = $usages->evaluateAndDeactivate()->filter({i| + let p1 = $i->resolveParameterType(0); + let p2 = $i.parametersValues->at(1)->resolveValueType(); + + !$p1->castCompatibleTypes($p2); + }); +} + +function meta::analytics::quality::model::domain::findBadInstanceOfChecks(fns:List>[1]) : FunctionExpression[*] +{ + + let applications = [instanceOf_Any_1__Type_1__Boolean_1_]; + + let usages = $fns->getUsages($applications); + + let invalidApplications = $usages->evaluateAndDeactivate()->filter({i| + let p1 = $i.parametersValues->at(0); + let p2 = $i.parametersValues->at(1); + + let p1t= $i->resolveParameterType(0); + + if(!$p2->instanceOf(InstanceValue), + | false, + {| + let targetType = $p2->cast(@InstanceValue).values->toOne()->cast(@Type); + + (!$p1t->castCompatibleTypes($targetType)); + }); + }); +} + +function meta::analytics::quality::model::domain::resolveParameterType(fe : FunctionExpression[1], index : Integer[1]) : Type[1] +{ + let fParams = $fe.func->functionType().parameters; + assert($fParams->size() > $index); + + let pGT = $fParams->at($index).genericType; + + let gt = if($pGT.rawType->isNotEmpty() && $pGT.rawType != Any, + | $pGT, + | ^GenericType(rawType = $fe.parametersValues->at($index)->resolveValueType()) + ); + + if($gt->isEmpty() || $gt.rawType->isEmpty(), + | Any, + | $gt.rawType->toOne() + ); +} + +function meta::analytics::quality::model::domain::resolveValueType(vs : ValueSpecification[1]) : Type[1] +{ + let t = $vs->match([ + v : ValueSpecification[1]| $v.genericType.rawType + ]); + + if($t->isEmpty(), + | Any, + | $t->toOne() + ); +} + + +function meta::analytics::quality::model::domain::findUnnecessaryToOneFEs(fns:List>[1]) : FunctionExpression[*] +{ + let applications = [toOne_T_MANY__T_1_]; + + let usages = $fns->getUsages($applications); + + + $usages->evaluateAndDeactivate() + ->filter({i| + $i.parametersValues->toOne().multiplicity == PureOne; + }); +} + +function <> meta::analytics::quality::model::domain::castCompatibleTypes(from: Type[1], to: Type[1]) : Boolean[1] +{ + if($from->in([$to, Any]) || $from == Nil || $to == Any, + | true, + | + if ($from.generalizations->size() == 0 && $to.specializations->size() == 0, + | false, + | $to->_subTypeOf($from) || $from->_subTypeOf($to) || $from->getLeafTypes()->remove($from)->exists(x|$x->_subTypeOf($to)) + ) + ) +} + +function meta::analytics::quality::model::domain::castNotNecessary(from: Type[1], to: Type[1]) : Boolean[1] +{ + if($to->in([$from, Any]), + | true, + | + $from->_subTypeOf($to) + ); +} + + +function meta::analytics::quality::model::domain::findUnnecessaryIfs(fns:List>[1]):FunctionExpression[*] +{ + + let applications = [if_Boolean_1__Function_1__Function_1__T_m_]; + + let usages = $fns->getUsages($applications); + + $usages->evaluateAndDeactivate()->filter(a | + let condition = $a.parametersValues->at(0); + $condition->match([ + i:InstanceValue[1] | $i.values->at(0)->instanceOf(Boolean), + a:Any[1] | false + ]); + ); +} + +function meta::analytics::quality::model::domain::findUnnecessaryIfs2(fns:List>[1]):FunctionExpression[*] +{ + let applications = [if_Boolean_1__Function_1__Function_1__T_m_]; + + let usages = $fns->getUsages($applications); + + $usages->evaluateAndDeactivate()->filter(a | + let statements = $a.parametersValues->tail()->cast(@InstanceValue).values->cast(@LambdaFunction).expressionSequence; + let truth = $statements->at(0); + let falsy = $statements->at(1); + + // If the return values of the if conditions are constant booleans and + // 1) They are the same, then the If is unnecessary + // 2) They are different to each other, we should just return the condition or the !condition + + $truth->match([ + i:InstanceValue[1] | $i.values == true || $i.values == false, + a:Any[*] | false + ]) + && + $falsy->match([ + i:InstanceValue[1] | $i.values == true || $i.values == false, + a:Any[*] | false + ]); + ); +} + + +function meta::analytics::quality::model::domain::unnecessaryTrueComparisons(fns:List>[1]):FunctionExpression[*] +{ + let applications = [equal_Any_MANY__Any_MANY__Boolean_1_]; + + let usages = $fns->getUsages($applications); + + $usages->evaluateAndDeactivate()->filter(a | + let p1 = $a.parametersValues->at(0); + let p2 = $a.parametersValues->at(1); + + let isBooleanFunction = $p1->match([ + f:SimpleFunctionExpression[1] | $f.genericType.rawType == Boolean,//.typeArguments.rawType->cast(@FunctionType).returnType.rawType == Boolean, + a:Any[1] | false + ]); + + let isTrueValue = $p2->match([ + i:InstanceValue[1] | $i.values->size() == 1 && $i.values->at(0) == true, + a:Any[1] | false + ]); + + $isBooleanFunction && $isTrueValue; + ); +} + + +function meta::analytics::quality::model::domain::findUnnecessaryMapToProperty(fns:List>[1]):FunctionExpression[*] +{ + let applications = [map_T_m__Function_1__V_m_]; + + let usages = $fns->getUsages($applications); + + $usages->evaluateAndDeactivate()->filter(m| + let param0 = $m.parametersValues->at(0); + let param1 = $m.parametersValues->at(1); + if (!$param0.genericType.typeArguments->isEmpty() || $param1->instanceOf(InstanceValue) == false, + | false, + | + let param1Value = $param1->cast(@InstanceValue).values->toOne(); + if ($param1Value->instanceOf(LambdaFunction) == false, + | false, + | let lambda = $param1Value->cast(@LambdaFunction); + let firstExpr = $lambda.expressionSequence->first(); + if ($lambda.expressionSequence->size() != 1 || !$firstExpr->toOne()->instanceOf(SimpleFunctionExpression), + | false, + | let f = $firstExpr->cast(@SimpleFunctionExpression).func->toOne(); + $f->instanceOf(Property) && !$f->instanceOf(QualifiedProperty); //Do qualified properties with no args work? + ); + ); + ); + ); +} + + + +function {rule.rule = 'Unnecessary Cast', + rule.description = 'Check for unnecessary casts (e.g. \'hello\'->cast(@String))', + rule.severity = 'Low', + rule.category = 'Quality'} +meta::analytics::quality::model::domain::findUnnecessaryCasts(fns:List>[1]) : Pair[*] +{ + + + let applications = [cast_Any_m__T_1__T_m_]; + + let usages = $fns->meta::analytics::quality::model::domain::getUsages($applications); + + $usages->evaluateAndDeactivate()->map({i| + let p1t = $i->meta::analytics::quality::model::domain::resolveParameterType(0); + let p2t = $i.parametersValues->at(1)->meta::analytics::quality::model::domain::resolveValueType(); + + if(!$p1t->meta::analytics::quality::model::domain::castNotNecessary($p2t), + | [], + | + let message = ('Possible unnecesary cast (from ' + $p1t->makeString() + ' to ' + $p2t->makeString() + ')'); + let result = ^CheckResult(isValid=false,message=$message); + pair($i,$result); + ); + }); +} + + +function {rule.rule = 'Unnecessary let', + rule.description ='Check for functions where last statement is a let', + rule.severity = 'Medium', + rule.category = 'Quality'} +meta::analytics::quality::model::domain::findUnnecessaryLetFunctionsCheck(fns:List>[1]) : Pair,CheckResult>[*] +{ + $fns.values->map(f| + let last = $f->evaluateAndDeactivate().expressionSequence->evaluateAndDeactivate()->last()->toOne(); + let doesEndInLet = $last->instanceOf(FunctionExpression) && $last->cast(@FunctionExpression).func == letFunction_String_1__T_m__T_m_; + let message = 'function ' + $f.functionName->makeString() + ' ends with an unnecessary let statement'; + let result = ^CheckResult(isValid = !$doesEndInLet, message = $message); + pair($f,$result); + ); +} + + +function {doc.doc = 'Find all concretely defined functions which are not called anywhere.'} +meta::analytics::quality::model::domain::findAllUncalledFunctions():ConcreteFunctionDefinition[*] +{ + ConcreteFunctionDefinition.all()->filter(f | !$f->isTestElement() && !$f->isFunctionReferenced()) +} + +function <> + {rule.rule = 'Assert with dynamic message', + rule.description ='Avoid using dynamic messages as they can be "expensive", even if the assertion passes', + rule.severity = 'Medium', + rule.category = 'Quality'} +meta::analytics::quality::model::domain::findPotentiallyExpensiveAssertions(fns:List>[1]) : Pair[*] +{ + let applications = [assert_Boolean_1__String_1__Boolean_1_]; + + let usages = $fns->meta::analytics::quality::model::domain::getUsages($applications); + + let expensiveUsages = $usages->evaluateAndDeactivate()->filter(m| + let param1 = $m.parametersValues->at(1); + !$param1->instanceOf(InstanceValue); + ); + + $expensiveUsages->map(f| + let message = 'Assertion message is dynamically built'; + let result = ^CheckResult(isValid = false, message = $message); + pair($f,$result); + ); +} \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/propertyChecks.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/propertyChecks.pure new file mode 100644 index 00000000000..d1357de52dc --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/propertyChecks.pure @@ -0,0 +1,312 @@ +import meta::analytics::quality::model::*; +import meta::analytics::quality::*; +import meta::analytics::quality::model::domain::*; + +function meta::analytics::quality::model::domain::propertyRules():Rule>[*] +{ + [ meta::analytics::quality::model::domain::classPropertyShouldStartWithLowerLetter_AbstractProperty_1__CheckResult_MANY_, + meta::analytics::quality::model::domain::classPropertyShouldNotStartWithClassName_AbstractProperty_1__CheckResult_MANY_, + meta::analytics::quality::model::domain::classBooleanPropertyShouldStartWithIsOrHasOrEndsWithFlag_AbstractProperty_1__CheckResult_MANY_, + meta::analytics::quality::model::domain::classPropertyStartingWithIsOrHasShouldBeBoolean_AbstractProperty_1__CheckResult_MANY_, + meta::analytics::quality::model::domain::classPropertyEndingWithFlagShouldBeBoolean_AbstractProperty_1__CheckResult_MANY_, + meta::analytics::quality::model::domain::classPropertyWithToManyMultiplicityAreNamedCorrectly_AbstractProperty_1__CheckResult_MANY_, + meta::analytics::quality::model::domain::classPropertyWithToOneMultiplicityAreNamedCorrectly_AbstractProperty_1__CheckResult_MANY_, + meta::analytics::quality::model::domain::classPropertyIntegersWithToOneMultiplicityAreNamedCorrectly_AbstractProperty_1__CheckResult_MANY_ + // meta::analytics::quality::qualifiedPropertiesShouldBeTested_AbstractProperty_1__CheckResult_MANY_ + ]->map(rule|createRule($rule)->cast(@Rule>)) + +} + + +function + { rule.rule = 'Invalid Property Names', + rule.description ='Property name should start with lower letter and in camelCase', + rule.severity = 'Medium', + rule.category = 'Modelling'} + +meta::analytics::quality::model::domain::classPropertyShouldStartWithLowerLetter(p: AbstractProperty[1]):CheckResult[*] +{ + if($p->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype(), + | ^CheckResult(isValid=true, message='Skipping ' + $p.name->toOne() + ' as it is generated milestone property'), + | + let issues = if(!$p.name->toOne()->meta::pure::functions::string::substring(0,1)->isLowerCase(), + | 'should start with lower case', + | []) + ->concatenate(if ($p.name->contains('_'), + | 'should not contain \'_\'', + | [] + )); + + let message = if($issues->isEmpty(), + | 'Property name (' + $p.name->toOne() + ') matches required standards', + | 'Property name (' + $p.name->toOne() + ') does not match required standards: ' + $issues->joinStrings(';') + ); + + ^CheckResult(isValid=$issues->isEmpty(), message=$message); + ); + +} + + +function + { rule.rule = 'Invalid Property Names(class name)', + rule.description ='Property name should not start with class name', + rule.severity = 'Medium', + rule.category = 'Modelling', + doc.doc = 'Returns true if the property names does not start with class name'} +meta::analytics::quality::model::domain::classPropertyShouldNotStartWithClassName(p: AbstractProperty[1]):CheckResult[*] +{ + if($p->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype(), + | ^CheckResult(isValid=true, message='Skipping ' + $p.name->toOne() + ' as it is generated milestone property'), + | + let passed= !$p.name->toOne()->toLower() + ->meta::pure::functions::string::startsWith($p.owner.name->toOne()->toLower()); + + let message = if($passed, + | 'Property name (' + $p.name->toOne() + ') matches required standards', + | 'Property name (' + $p.name->toOne() + ') should not start with class name (' + $p.owner.name->toOne() + ')' + $p.name->toOne() + ); + + ^CheckResult(isValid=$passed, message=$message); + ); +} + + + +function + { rule.rule = 'Invalid Boolean Property Name', + rule.description = 'Boolean property should start with \'is\' or \'has\' or ends with \'Flag\'', + rule.severity = 'Medium', + rule.category = 'Modelling', + doc.doc = 'Returns true if the boolean property starts with \'is\' or \'has\''} +meta::analytics::quality::model::domain::classBooleanPropertyShouldStartWithIsOrHasOrEndsWithFlag(p: AbstractProperty[1]):CheckResult[*] +{ + if($p->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype(), + | ^CheckResult(isValid=true, message='Skipping ' + $p.name->toOne() + ' as it is generated milestone property'), + | + let passed = if($p.genericType->meta::pure::metamodel::serialization::grammar::printGenericType()->equal('Boolean'), + |$p.name->toOne()->meta::pure::functions::string::startsWith('is') + || $p.name->toOne()->meta::pure::functions::string::startsWith('has') + || $p.name->toOne()->meta::pure::functions::string::endsWith('Flag') + || $p.name->toOne()->meta::pure::functions::string::endsWith('Flags'), + |true;); + + let message = if($passed, + | 'Property name (' + $p.name->toOne() + ') matches required standards', + | 'Property is of type boolean so the name (' + $p.name->toOne() + ') should start with \'is\' / \'has\' or end with \'Flag\' ' + $p.name->toOne() + ); + + ^CheckResult(isValid=$passed, message=$message); + ); +} + + +function + { rule.rule = 'Invalid Boolean Flag Property Type', + rule.description = 'Property name ending with Flag should be Boolean', + rule.severity = 'Medium', + rule.category = 'Modelling', + doc.doc = 'Returns true if the property name ending in Flag is Boolean type'} +meta::analytics::quality::model::domain::classPropertyEndingWithFlagShouldBeBoolean(p: AbstractProperty[1]):CheckResult[*] +{ + if($p->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype(), + | ^CheckResult(isValid=true, message='Skipping ' + $p.name->toOne() + ' as it is generated milestone property'), + | + let passed = !$p.name->toOne()->toLower()->meta::pure::functions::string::endsWith('flag') + || $p.genericType->meta::pure::metamodel::serialization::grammar::printGenericType()->equal('Boolean'); + + let message = if($passed, + | 'Property name (' + $p.name->toOne() + ') matches required standards', + | 'Property name (' + $p.name->toOne() + ') ends with \'flag\' but is not a boolean' + $p.name->toOne() + ); + + ^CheckResult(isValid=$passed, message=$message); + ) +} + +function + { rule.rule = 'Invalid Boolean Is Property Type', + rule.description = 'Property name starting with Is/Has should be Boolean', + rule.severity = 'Medium', + rule.category = 'Modelling', + doc.doc = 'Returns true if the property name starting with Is or Has is Boolean type'} +meta::analytics::quality::model::domain::classPropertyStartingWithIsOrHasShouldBeBoolean(p: AbstractProperty[1]):CheckResult[*] +{ + let ignoreWords = [ + 'issue', + 'issuance' + + ]; + + if($p->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype() , + | ^CheckResult(isValid=true, message='Skipping ' + $p.name->toOne() + ' as it is generated milestone property'), + | if($p.genericType->meta::pure::metamodel::serialization::grammar::printGenericType()->equal('Boolean') || $ignoreWords->exists(w|$p.name->toOne()->toLower()->startsWith($w)), + | ^CheckResult(isValid=true, message='Skipping ' + $p.name->toOne() + ' already boolean or start of name to be ignored'), + | + let passed = !$p.name->toOne()->meta::pure::functions::string::startsWith('is') && !$p.name->toOne()->meta::pure::functions::string::startsWith('has'); + + let message = if($passed, + | 'Property name (' + $p.name->toOne() + ') matches required standards', + | 'Property name (' + $p.name->toOne() + ') starts with Is or Has but is not a boolean ' + $p.genericType->meta::pure::metamodel::serialization::grammar::printGenericType()->toOne() + ); + + ^CheckResult(isValid=$passed, message=$message); + )); +} + +// function +// <> +// { rule.rule = 'Check qualfied properties are tested', +// rule.description = 'Checks that qualified properties are used within at least one test', +// rule.severity = 'High', +// rule.category = 'Testing', +// doc.doc = 'Returns true if the property is either tested or not a qualfied property'} +// meta::analytics::quality::qualifiedPropertiesShouldBeTested(p: AbstractProperty[1]):CheckResult[*] +// { +// if($p->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype(), +// | ^CheckResult(isValid=true, message='Skipping ' + $p.name->toOne() + ' as it is generated milestone property'), +// | if(!$p->instanceOf(QualifiedProperty), +// | ^CheckResult(isValid=true, message='Skipping ' + $p.name->toOne() + ' as it is not a qualified property'), +// | +// if($p->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype(), +// | ^CheckResult(isValid=true, message='Skipping ' + $p.name->toOne() + ' as auto generated milestone property'), +// | if($p->isFunctionTested(), +// | ^CheckResult(isValid=true, message='Property ' + $p.name->toOne() + ' is tested'), +// | ^CheckResult(isValid=false, message='Property ' + $p.name->toOne() + ' not found in any test') +// ) +// ) +// ) +// ); +// } + +function + { rule.rule = 'Invalid naming style for "to many" properties', + rule.description = 'Checks that properties with "to many" multiplicity reflect this in the name', + rule.severity = 'Low', + rule.category = 'Modelling', + doc.doc = 'Returns true if the property is named correctly'} +meta::analytics::quality::model::domain::classPropertyWithToManyMultiplicityAreNamedCorrectly(p: AbstractProperty[1]):CheckResult[*] +{ + //http://grammar.ccc.commnet.edu/grammar/plurals.htm + + let ignoreEndings = [ + 'cacti', + 'children', + 'criteria', + 'deer', + 'foci', + 'fungi', + 'geese', + 'men', + 'mice', + 'nuclei', + 'people', + 'phenomena', + 'syllabi', + 'teeth', + 'women' + ]; + + let result = if(!($p.multiplicity->isToOne() || $p.multiplicity->isZeroOne() || $p->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype()), + | $p.name->toOne()->toLower()->endsWith('s') || $ignoreEndings->exists(w|$p.name->toOne()->toLower()->endsWith($w)), + | true + ); + + + let message = if(!$result, + | 'Property ' + $p.name->toOne() + ' has multiplicity ' + $p.multiplicity->makeString() + ' but name does not suggest multiplicity (i.e. should likely be plural)', + | 'Property ' + $p.name->toOne() + ' is correctly named' + ); + + ^CheckResult(isValid=$result, message=$message); +} + +function + { rule.rule = 'Invalid naming style for singleton properties', + rule.description = 'Checks that singleton properties are not pluralised', + rule.severity = 'Low', + rule.category = 'Modelling', + doc.doc = 'Returns true if the property is named correctly'} +meta::analytics::quality::model::domain::classPropertyWithToOneMultiplicityAreNamedCorrectly(p: AbstractProperty[1]):CheckResult[*] +{ + //http://grammar.ccc.commnet.edu/grammar/plurals.htm + let pluralEndings = [ + 'cacti', + 'children', + 'criteria', + 'deer', + 'statii', + 'foci', + 'fungi', + 'geese', + 'men', + 'mice', + 'nuclei', + 'people', + 'phenomena', + 'syllabi', + 'teeth', + 'women' + ]; + + let ignoreEndings = [ + 'days', + 'years', + 'hours', + 'seconds', + 'units', + 'ss', + 'status', + 'basis', + 'plus', + 'kerberos', + 'previous', + 'comments' + + ]; + + let result = if((($p.multiplicity->isToOne() || $p.multiplicity->isZeroOne()) && !$p->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype()), + | (!($p.name->toOne()->toLower()->endsWith('s') || $pluralEndings->exists(w|$p.name->toOne()->toLower()->endsWith($w))) || $ignoreEndings->exists(w|$p.name->toOne()->toLower()->endsWith($w))), + | true + ); + + + let message = if(!$result, + | 'Property ' + $p.name->toOne() + ' has multiplicity ' + $p.multiplicity->makeString() + ' but name suggests multiples (i.e. should not be plural)', + | 'Property ' + $p.name->toOne() + ' is correctly named' + ); + + ^CheckResult(isValid=$result, message=$message); +} + +function + { rule.rule = 'Invalid naming style for singleton properties', + rule.description = 'Checks that singleton properties that look like counts are integers', + rule.severity = 'Low', + rule.category = 'Modelling', + doc.doc = 'Returns true if the property is named correctly'} +meta::analytics::quality::model::domain::classPropertyIntegersWithToOneMultiplicityAreNamedCorrectly(p: AbstractProperty[1]):CheckResult[*] +{ + //http://grammar.ccc.commnet.edu/grammar/plurals.htm + // days/hours/seconds/units - OK if integer (no. of) + let pluralEndings = [ + 'days', + 'years', + 'hours', + 'seconds', + 'units' + ]; + + let result = if((($p.multiplicity->isToOne() || $p.multiplicity->isZeroOne()) && !$p->meta::pure::milestoning::hasGeneratedMilestoningPropertyStereotype() && ( $pluralEndings->exists(w|$p.name->toOne()->toLower()->endsWith($w)))), + | ($p.genericType->meta::pure::metamodel::serialization::grammar::printGenericType()->equal('Integer')), + | true + ); + + + let message = if(!$result, + | 'Property ' + $p.name->toOne() + ' is of type ' + $p.genericType->meta::pure::metamodel::serialization::grammar::printGenericType()->makeString() + ', not integer, but name suggests it is a count', + | 'Property ' + $p.name->toOne() + ' is correctly named' + ); + + ^CheckResult(isValid=$result, message=$message); +} \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/associationChecksTest.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/associationChecksTest.pure new file mode 100644 index 00000000000..09fa21ec00f --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/associationChecksTest.pure @@ -0,0 +1,38 @@ +import meta::analytics::quality::tests::*; +import meta::analytics::quality::*; + +function <> meta::analytics::quality::model::domain::tests::associationNameShouldStartWithUpperCaseTest():Boolean[1] +{ + assertEquals(true, meta::analytics::quality::model::domain::associationNameShouldStartWithUpperCase(meta::analytics::quality::model::domain::tests::TestsInProgress_NoMatchForTest).isValid); + assertEquals(false, meta::analytics::quality::model::domain::associationNameShouldStartWithUpperCase(meta::analytics::quality::model::domain::tests::testsInProgress_BadCase).isValid); + assertEquals(false, meta::analytics::quality::model::domain::associationNameShouldStartWithUpperCase(meta::analytics::quality::model::domain::tests::TestsInProgressAnotherBadCase).isValid); + assertEquals(false, meta::analytics::quality::model::domain::associationNameShouldStartWithUpperCase(meta::analytics::quality::model::domain::tests::TestsInProgress_yetAnotherBadCase).isValid); +} + +Association +meta::analytics::quality::model::domain::tests::TestsInProgress_NoMatchForTest +{ + test: meta::analytics::quality::model::domain::tests::TestsInProgress[1]; + testMatch: meta::analytics::quality::model::domain::tests::NoMatchForTest[0..1]; +} + +Association +meta::analytics::quality::model::domain::tests::testsInProgress_BadCase +{ + testOne: meta::analytics::quality::model::domain::tests::TestsInProgress[1]; + testMatchOne: meta::analytics::quality::model::domain::tests::NoMatchForTest[0..1]; +} + +Association +meta::analytics::quality::model::domain::tests::TestsInProgressAnotherBadCase +{ + testTwo: meta::analytics::quality::model::domain::tests::TestsInProgress[1]; + testMatchTwo: meta::analytics::quality::model::domain::tests::NoMatchForTest[0..1]; +} + +Association +meta::analytics::quality::model::domain::tests::TestsInProgress_yetAnotherBadCase +{ + testThree: meta::analytics::quality::model::domain::tests::TestsInProgress[1]; + testMatchThree: meta::analytics::quality::model::domain::tests::NoMatchForTest[0..1]; +} diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/classChecksTest.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/classChecksTest.pure new file mode 100644 index 00000000000..cfb98aade56 --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/classChecksTest.pure @@ -0,0 +1,146 @@ +import apps::pure::quality::*; +import meta::analytics::quality::*; + +function <> meta::analytics::quality::model::domain::tests::entityNameShouldNotStartWithPackageNameTest():Boolean[1] +{ + assertEquals(false, meta::analytics::quality::model::domain::entityNameShouldNotStartWithPackageName(meta::analytics::quality::model::domain::tests::Tests).isValid); + assertEquals(false, meta::analytics::quality::model::domain::entityNameShouldNotStartWithPackageName(meta::analytics::quality::model::domain::tests::TestsInProgress).isValid); + assertEquals(true, meta::analytics::quality::model::domain::entityNameShouldNotStartWithPackageName(meta::analytics::quality::model::domain::tests::NoMatchForTest).isValid); + assertEquals(true, meta::analytics::quality::model::domain::entityNameShouldNotStartWithPackageName(meta::analytics::quality::model::domain::tests::NoMatchForTestAgain).isValid); +} + +function <> meta::analytics::quality::model::domain::tests::classNameShouldStartWithUpperCaseTest():Boolean[1] +{ + assertEquals(true, meta::analytics::quality::model::domain::classNameShouldStartWithUpperCase(meta::analytics::quality::model::domain::tests::Test).isValid); + assertEquals(false, meta::analytics::quality::model::domain::classNameShouldStartWithUpperCase(meta::analytics::quality::model::domain::tests::lowerCaseTest).isValid); + assertEquals(false, meta::analytics::quality::model::domain::classNameShouldStartWithUpperCase(meta::analytics::quality::model::domain::tests::lowerCaseTestWith_Underscore).isValid); + assertEquals(false, meta::analytics::quality::model::domain::classNameShouldStartWithUpperCase(meta::analytics::quality::model::domain::tests::UpperCaseTestWith_Underscore).isValid); +} + +function <> meta::analytics::quality::model::domain::tests::classHasAtLeastOneMandatoryPropertyTest():Boolean[1] +{ + assertEquals(true, meta::analytics::quality::model::domain::classHasAtLeastOneMandatoryProperty(meta::analytics::quality::model::domain::tests::Test).isValid); + assertEquals(false, meta::analytics::quality::model::domain::classHasAtLeastOneMandatoryProperty(meta::analytics::quality::model::domain::tests::EmptyClass).isValid); +} + +function <> meta::analytics::quality::model::domain::tests::allEntitiesAndPropertiesShouldHaveALongDescriptionTest():Boolean[1] +{ + assertEquals(false, meta::analytics::quality::model::domain::allEntitiesAndPropertiesShouldHaveALongDescription(meta::analytics::quality::model::domain::tests::Test).isValid); + assertEquals(true, meta::analytics::quality::model::domain::allEntitiesAndPropertiesShouldHaveALongDescription(meta::analytics::quality::model::domain::tests::TestClassWithDoc).isValid); +} + + +Class {doc.doc = 'test class'} +meta::analytics::quality::model::domain::tests::Tests +{ + {doc.doc = 'test name'} + testName : String[1]; + + {doc.doc = 'test Description'} + testDescription : String[1]; +} + +Class {doc.doc = 'test in progress class'} +meta::analytics::quality::model::domain::tests::TestsInProgress +{ + {doc.doc = 'test name'} + testName : String[1]; + + testDescription : String[1]; +} + +Class meta::analytics::quality::model::domain::tests::NoMatchForTest +{ + testName : String[1]; + testMatch : Boolean[1]; +} + +Class meta::analytics::quality::model::domain::tests::NoMatchForTestAgain +{ + testName : String[1]; + testMatch : Boolean[1]; +} + +Class {doc.doc = 'test class'} +meta::analytics::quality::model::domain::tests::TestClassWithDoc +{ + {doc.doc = 'test name'} + testName : String[1]; + + {doc.doc = 'test Description'} + testDescription : String[1]; +} + +Class meta::analytics::quality::model::domain::tests::TestClassWithoutDoc +{ + {doc.doc = 'test name'} + testName : String[1]; + + {doc.doc = 'test Description'} + testDescription : String[1]; +} + +Class {doc.doc = 'test in progress class'} +meta::analytics::quality::model::domain::tests::TestClassWithPropertiesMissingDoc +{ + {doc.doc = 'test name'} + testName : String[1]; + + testDescription : String[1]; +} + +Class meta::analytics::quality::model::domain::tests::TestClassWithAllDocsMissing +{ + testName : String[1]; + testMatch : Boolean[1]; +} + +Class meta::analytics::quality::model::domain::tests::Test +{ + testName : String[1]; + testMatch : Boolean[1]; +} + +Class meta::analytics::quality::model::domain::tests::lowerCaseTest +{ + testName : String[1]; + testMatch : Boolean[1]; +} + +Class meta::analytics::quality::model::domain::tests::lowerCaseTestWith_Underscore +{ + testName : String[1]; + testMatch : Boolean[1]; +} + +Class meta::analytics::quality::model::domain::tests::UpperCaseTestWith_Underscore +{ + testName : String[1]; + testMatch : Boolean[1]; +} + +Class meta::analytics::quality::model::domain::tests::EmptyClass +{ + +} + + + + +###Relational +Database meta::analytics::quality::model::domain::tests::TestTable +( + Table testName( name VARCHAR(10) PRIMARY KEY) +) + +###Mapping + +import meta::analytics::quality::model::domain::tests::*; + +Mapping meta::analytics::quality::model::domain::tests::TestTableMapping +( + Tests : Relational + { + testName: [TestTable]testName.name + } +) \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/enumerationChecksTest.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/enumerationChecksTest.pure new file mode 100644 index 00000000000..c5bd1df868b --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/enumerationChecksTest.pure @@ -0,0 +1,63 @@ +import meta::analytics::quality::tests::*; +import meta::analytics::quality::*; + +function <> meta::analytics::quality::model::domain::tests::enumerationCaseSensitivity():Boolean[1] +{ + assertEquals(true, meta::analytics::quality::model::domain::enumerationName(meta::analytics::quality::model::domain::tests::Valid).isValid); + assertEquals(false, meta::analytics::quality::model::domain::enumerationName(meta::analytics::quality::model::domain::tests::invalid1).isValid); + assertEquals(false, meta::analytics::quality::model::domain::enumerationName(meta::analytics::quality::model::domain::tests::Invalid_3).isValid); + assertEquals(true, meta::analytics::quality::model::domain::enumerationValue(meta::analytics::quality::model::domain::tests::Valid).isValid); + assertEquals(false, meta::analytics::quality::model::domain::enumerationValue(meta::analytics::quality::model::domain::tests::Invalid2).isValid); + assertEquals(false, meta::analytics::quality::model::domain::enumerationValue(meta::analytics::quality::model::domain::tests::Invalid).isValid); + assertEquals(false, meta::analytics::quality::model::domain::enumerationValue(meta::analytics::quality::model::domain::tests::Invalid1).isValid); + assertEquals(false, meta::analytics::quality::model::domain::enumerationValue(meta::analytics::quality::model::domain::tests::INVALID).isValid); +} + +Enum meta::analytics::quality::model::domain::tests::Valid +{ + AValidValue, + AnotherValidValue +} + +Enum {doc.doc='Enum name starts with lower case'} +meta::analytics::quality::model::domain::tests::invalid1 +{ + AValidValue, + AnotherValidValue +} + +Enum {doc.doc='Enum name contains underscore'} +meta::analytics::quality::model::domain::tests::Invalid_3 +{ + AValidValue, + AnotherValidValue +} + +Enum {doc.doc='Enum name all upper case'} +meta::analytics::quality::model::domain::tests::INVALID +{ + AValidValue, + AnotherValidValue +} + +Enum {doc.doc='Enum all upper case'} +meta::analytics::quality::model::domain::tests::Invalid +{ + AVV, + AnotherValidValue +} + +Enum {doc.doc='Enum contains underscore'} +meta::analytics::quality::model::domain::tests::Invalid1 +{ + A_Valid_Value, + AnotherValidValue +} + +Enum {doc.doc='Enum starts with lower case'} +meta::analytics::quality::model::domain::tests::Invalid2 +{ + AValidValue, + anotherValidValue +} + diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/functionChecksTest.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/functionChecksTest.pure new file mode 100644 index 00000000000..2f578fe1a0b --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/functionChecksTest.pure @@ -0,0 +1,223 @@ +import meta::analytics::quality::tests::*; +import meta::analytics::quality::model::*; +import meta::analytics::quality::model::domain::*; +import meta::relational::runtime::*; +import meta::analytics::quality::model::domain::tests::*; + +function {rule.ignore='Invalid Equality'} + meta::analytics::quality::model::domain::tests::pack1::ignoreMe():Boolean[1] +{ + 'this is not false, obviously' == false +} + + +function meta::analytics::quality::model::domain::tests::pack1::invalidEqualsFunc():Boolean[1] +{ + 'hello' == 1; +} + +function meta::analytics::quality::model::domain::tests::pack1::invalidNestedEqualsFunc():Boolean[1] +{ + + [1,2,3,4]->map(i | $i == 'hello' ); + 'hello' == 5; +} + +function meta::analytics::quality::model::domain::tests::pack1::invalidContainsFunc():Boolean[1] +{ + [1,2,3,4]->contains('hello'); +} + +function meta::analytics::quality::model::domain::tests::pack1::validContainsFunc():Boolean[1] +{ + true==true; + [1,2,3,4]->contains(2); +} + +function meta::analytics::quality::model::domain::tests::pack1::badUseOfSizeFunc1():Boolean[1] +{ + ['hello']->size() == 0; +} + +function meta::analytics::quality::model::domain::tests::pack1::badUseOfSizeFunc2():Boolean[1] +{ + ['hello']->size() != 0; +} + +function meta::analytics::quality::model::domain::tests::pack1::badUseOfSizeFunc3():Boolean[1] +{ + ['hello']->size() > 0; +} + +function meta::analytics::quality::model::domain::tests::pack1::invalidMatchStatement():Integer[1] +{ + ['hello']->match([i:Integer[1] | $i]); +} + +function meta::analytics::quality::model::domain::tests::pack1::validMatchStatement():String[1] +{ + ['hello']->match([i:String[1] | $i]); +} + +function meta::analytics::quality::model::domain::tests::pack1::invalidInstanceOfFunc():Boolean[1] +{ + 'hello'->instanceOf(Float); +} + +function meta::analytics::quality::model::domain::tests::pack2::validInstanceOfFunc():Boolean[1] +{ + let x = 5; + $x->cast(@Any)->instanceOf(Number); + + pair(1,'hello')->instanceOf(Pair); +} + + +function meta::analytics::quality::model::domain::tests::pack1::unnecessaryUseOfCastFunc():String[1] +{ + 'hello'->cast(@String); +} + +function meta::analytics::quality::model::domain::tests::pack1::unnecessaryUseOfToOneFunc():String[1] +{ + 'hello'->toOne(); +} + +function meta::analytics::quality::model::domain::tests::pack1::unnecessaryComparisonToBooleanFunc():String[1] +{ + if(meta::analytics::quality::model::domain::tests::pack1::booleanFunc() == true, + | 'yes', + | 'no' + ); +} + +function meta::analytics::quality::model::domain::tests::pack1::booleanFunc():Boolean[1] +{ + true; +} + +function meta::analytics::quality::model::domain::tests::pack1::unnecessaryMapToGetProperty():Integer[*] +{ + [pair(1,2), pair(3,4)]->map(p|$p.first); +} + +function meta::analytics::quality::model::domain::tests::pack1::myFuncWithNotNecessaryLet():Boolean[1] +{ + let a = true; +} + +function <> meta::analytics::quality::model::domain::tests::findBadInstanceOfChecksTest():Boolean[1] +{ + let results = list([meta::analytics::quality::model::domain::tests::pack1::invalidInstanceOfFunc__Boolean_1_, + meta::analytics::quality::model::domain::tests::pack2::validInstanceOfFunc__Boolean_1_] + )->findBadInstanceOfChecks(); + + println($results->map(x|let si = $x->sourceInformation(); $si.source->makeString() + '@' + $si.line->makeString() + 'c' + $si.column->makeString();)); + + assertEquals(1, $results->size()); + + true; +} + + +function <> meta::analytics::quality::model::domain::tests::findUsagesTest():Boolean[1] +{ + let qc = [equal_Any_MANY__Any_MANY__Boolean_1_, is_Any_1__Any_1__Boolean_1_]; + assertSize(meta::analytics::quality::model::domain::tests::pack1::invalidNestedEqualsFunc__Boolean_1_->getUsages($qc), 2); + assertSize(meta::analytics::quality::model::domain::tests::pack1::invalidEqualsFunc__Boolean_1_->getUsages($qc), 1); + assertEmpty(meta::analytics::quality::model::domain::tests::pack1::unnecessaryMapToGetProperty__Integer_MANY_->getUsages($qc)); + assertEmpty(meta::analytics::quality::model::domain::tests::pack1::booleanFunc__Boolean_1_->getUsages($qc)); +} + +function <> meta::analytics::quality::model::domain::tests::findInvalidEqualityComparisonsTest():Boolean[1] +{ + let results = list(meta::analytics::quality::model::domain::tests::pack1::invalidEqualsFunc__Boolean_1_)->validEqualityComparisons(); + assertEquals(1, $results->size()); + $results->map(result| assertFalse($result.second.isValid,| $result.second.message)); + + true; +} + +function <> meta::analytics::quality::model::domain::tests::findInvalidNestedEqualityComparisonsTest():Boolean[1] +{ + let fun = meta::analytics::quality::model::domain::tests::pack1::invalidNestedEqualsFunc__Boolean_1_; + let funs= $fun->sourceInformation(); + let results = list($fun)->validEqualityComparisons(); + assertEquals(2, $results->size()); + $results->map(result| assertFalse($result.second.isValid,| $result.second.message)); + + $results->map(r | let si = $r.first->sourceInformation(); + assert($si.source == $funs.source && + ($funs.startLine <= $si.startLine ) && ( $si.startLine <=$funs.endLine ), | $si.source->toOne()) ; + ); + true; +} + + +function <> meta::analytics::quality::model::domain::tests::findInvalidContainsComparisonsTest():Boolean[1] +{ + + let funs = [ meta::analytics::quality::model::domain::tests::pack1::invalidContainsFunc__Boolean_1_, + meta::analytics::quality::model::domain::tests::pack1::booleanFunc__Boolean_1_]; + let invalidApplications = list($funs)->findInvalidContainsComparisons(); + + assertEquals(1, $invalidApplications->size()); + +} + +function <> meta::analytics::quality::model::domain::tests::findInvalidMatchUsageTest():Boolean[1] +{ + assertEmpty(list(meta::analytics::quality::model::domain::tests::pack1::invalidContainsFunc__Boolean_1_)->findInvalidMatchUsage()); + assertEmpty(list(meta::analytics::quality::model::domain::tests::pack1::booleanFunc__Boolean_1_)->findInvalidMatchUsage()); + assertEmpty(list(meta::analytics::quality::model::domain::tests::pack1::validMatchStatement__String_1_)->findInvalidMatchUsage()); + assertSize(list(meta::analytics::quality::model::domain::tests::pack1::invalidMatchStatement__Integer_1_)->findInvalidMatchUsage(), 1); +} + + +function <> meta::analytics::quality::model::domain::tests::getUsages(srcFn :FunctionDefinition[1],fns:Function[*]):FunctionExpression[*] +{ + meta::analytics::quality::model::domain::getUsages(list($srcFn), $fns); +} + + +function <> meta::analytics::quality::model::domain::tests::findUnnecesaryLetsTest():Boolean[1] +{ + + let results = list(meta::analytics::quality::model::domain::tests::pack1::invalidContainsFunc__Boolean_1_) + ->findUnnecessaryLetFunctionsCheck(); + + assertSize($results,1); +} + +function <> meta::analytics::quality::model::domain::tests::castCompatibleTypesTest():Boolean[1] +{ + + assert(meta::pure::metamodel::type::Type->castCompatibleTypes(meta::pure::metamodel::type::Class), 'Type vs. Class'); + assert(meta::pure::metamodel::extension::AnnotatedElement->castCompatibleTypes(meta::pure::metamodel::type::Class), 'AnnotatedElement vs. Class'); + // assert(meta::relational::runtime::DatabaseConnection->castCompatibleTypes(meta::relational::datalake::runtime::LakeWarehouseConnectionInfo), 'DatabaseConnection vs. LakeWarehouseConnectionInfo'); + assert(meta::pure::store::Store->castCompatibleTypes(meta::relational::metamodel::Database), 'Store vs. DB'); + assert(meta::relational::metamodel::Database->castCompatibleTypes(meta::pure::store::Store), 'Db vs. Store'); + assert(Number->castCompatibleTypes(Float), 'Number vs Float'); + assert(Float->castCompatibleTypes(Number), 'Float vs Number'); + assert(!String->castCompatibleTypes(Number), 'String vs Number'); +} + +function <> meta::analytics::quality::model::domain::tests::equalityCompatibleTypesTest():Boolean[1] +{ + + // assert(meta::relational::runtime::DatabaseConnection->equalityCompatibleTypes(meta::relational::datalake::runtime::LakeWarehouseConnectionInfo), 'DatabaseConnection vs. LakeWarehouseConnectionInfo'); + assert(TabularDataSet->equalityCompatibleTypes(Any), 'TabularDataSet vs. Any'); + assert(TabularDataSet->equalityCompatibleTypes(TabularDataSet), 'TabularDataSet vs. TabularDataSet'); + assert(Float->equalityCompatibleTypes(Integer), 'Float vs. Integer'); + assert(!Float->equalityCompatibleTypes(String), 'Float vs. String'); + assert(VariableExpression->equalityCompatibleTypes(ValueSpecification), 'VariableExpression vs. ValueSpecification'); +} + +function <> meta::analytics::quality::model::domain::tests::castNotNecessaryTest():Boolean[1] +{ + + assert(!meta::pure::store::Store->castNotNecessary(meta::relational::metamodel::Database), 'Store vs. DB'); + assert(meta::relational::metamodel::Database->castNotNecessary(meta::pure::store::Store), 'Db vs. Store'); + assert(!Number->castNotNecessary(Float), 'Number vs Float'); + assert(Float->castNotNecessary(Number), 'Float vs Number'); +} \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/propertyChecksTest.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/propertyChecksTest.pure new file mode 100644 index 00000000000..03a35bae7f5 --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/propertyChecksTest.pure @@ -0,0 +1,40 @@ +import apps::pure::quality::*; +import meta::analytics::quality::*; +import meta::analytics::quality::model::domain::tests::*; +import meta::analytics::quality::model::*; + +Class meta::analytics::quality::model::domain::tests::MyOtherTestClass +{ + name: String[1]; + + nameReversed() { + $this.name->chunk(1)->reverse()->joinStrings() + }:String[1]; + + nameReversed_Untested() { + $this.nameReversed() + }:String[1]; + +} + +function <> meta::analytics::quality::model::domain::tests::myOtherTestClassNameReversed():Boolean[1] +{ + assertEquals('cba', ^MyOtherTestClass(name='abc').nameReversed()); +} + + +// function <> meta::analytics::quality::tests::qualifiedPropertiesShouldBeTestedTest():Boolean[1] +// { +// let v = runClassPropertyQualityCheck(MyOtherTestClass, qualifiedPropertiesShouldBeTested_AbstractProperty_1__CheckResult_MANY_, true); +// let failedMessages = $v.detail->filter(v|!$v.isValid).message; + +// assertEquals(['Property nameReversed_Untested not found in any test'], $failedMessages); +// } + + +function <> meta::analytics::quality::model::domain::tests::runClassPropertyQualityCheck(c : Class[1], ruleFunction: FunctionDefinition<{AbstractProperty[1]->Any[*]}>[1], forceNotSkipTestsFlag : Boolean[1]) : ViolationInstance[*] +{ + let propRules = meta::analytics::quality::model::domain::createRule($ruleFunction)->cast(@Rule>)->map(r|if($forceNotSkipTestsFlag, | ^$r(hasSkipTestsFlag=false), | $r)); + + let v = meta::analytics::quality::model::domain::runClassPropertyQualityChecks(MyOtherTestClass, $propRules); +} \ No newline at end of file diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/testQuality.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/testQuality.pure new file mode 100644 index 00000000000..a1ec3c9e3e3 --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/tests/testQuality.pure @@ -0,0 +1,265 @@ +import apps::pure::quality::tests::*; +import meta::relational::tests::*; +import meta::relational::runtime::*; +import meta::analytics::quality::model::*; + +Class meta::analytics::quality::model::domain::tests::myTestClass +{ + Name: String[1]; + myTestClassProperty: Boolean[1]; + myTestFlag:String[1]; + myInvalidDays:String[1]; + hasWrongType:String[1]; + myBadToManyProperty:String[*]; + myBadChildren:String[1]; + myBadSingletonProperties:String[0..1]; +} + + +Class {rule.ignore='Invalid Property Names'} //incorrect rule to ignore + meta::analytics::quality::model::domain::tests::testClassWithIgnoreFlags +{ + {rule.ignore='Invalid Property Names'} + Name: String[1]; + + {rule.ignore='Invalid Boolean Flag Property Type,Invalid Property Names'} + TestFlag: String[1]; + testClassProperty: Boolean[1]; + aTestFlag(){ 'lala' == false }:Boolean[1]; + + {rule.ignore='Invalid Equality'} + anotherTestFlag(){ 'this is not false, obviously' == false }:Boolean[1]; + +} + + + + +Class meta::analytics::quality::model::domain::tests::Employee +{ + name: Boolean[1]; +} + +Class meta::analytics::quality::model::domain::tests::Firm +{ + employeesFlag: meta::analytics::quality::model::domain::tests::Employee[0..1]; +} + +Class meta::analytics::quality::model::domain::tests::UntestedClass +{ + foo:String[1]; +} + +Class meta::analytics::quality::model::domain::tests::NoModelQualityViolation +{ + validChildren: String[*]; + kerberos: String[1]; + issuer: String[1]; + issuanceDate: Date[1]; + name: String[1]; + validationFlag: Boolean[1]; + maturityYears: Integer[0..1]; + isValid: Boolean[1]; + hasValidName: Boolean[1]; + validationFlags : Boolean[*]; +} + +function <> meta::analytics::quality::model::domain::tests::testClassNameShouldStartWithUpperCase():Boolean[1] +{ + assert(meta::analytics::quality::model::domain::tests::Employee->meta::analytics::quality::model::domain::classNameShouldStartWithUpperCase()->toOne().isValid); + assertFalse(meta::analytics::quality::model::domain::tests::myTestClass->meta::analytics::quality::model::domain::classNameShouldStartWithUpperCase()->toOne().isValid); +} + +// function <> meta::analytics::quality::tests::testClassShouldBeIncludedInADiagram():Boolean[1] +// { +// assert(meta::analytics::quality::tests::Employee->meta::analytics::quality::classIsIncludedInADiagram()->toOne().isValid); +// assertFalse(meta::analytics::quality::tests::myTestClass->meta::analytics::quality::classIsIncludedInADiagram()->toOne().isValid); +// } + +// function <> apps::pure::quality::tests::testClassShouldBeIncludedInATest():Boolean[1] +// { +// assert(meta::analytics::quality::tests::myTestClass->apps::pure::quality::classIsIncludedInATest()->toOne().isValid); +// assertFalse('apps::pure::quality::tests::UntestedClass'->pathToElement()->cast(@Class)->apps::pure::quality::classIsIncludedInATest()->toOne().isValid); +// } + +function <> meta::analytics::quality::model::domain::tests::testClassPropertyShouldStartWithLowerLetter():Boolean[1] +{ + assert(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'myTestFlag')->toOne()->meta::analytics::quality::model::domain::classPropertyShouldStartWithLowerLetter()->toOne().isValid); + assertFalse(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'Name')->toOne()->meta::analytics::quality::model::domain::classPropertyShouldStartWithLowerLetter()->toOne().isValid); +} + +function <> meta::analytics::quality::model::domain::tests::testClassPropertyShouldNotStartWithClassName():Boolean[1] +{ + assert(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'Name')->toOne()->meta::analytics::quality::model::domain::classPropertyShouldNotStartWithClassName()->toOne().isValid); + assertFalse(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'myTestClassProperty')->toOne()->meta::analytics::quality::model::domain::classPropertyShouldNotStartWithClassName()->toOne().isValid); +} + +function <> meta::analytics::quality::model::domain::tests::testClassBooleanPropertyShouldStartWithIsOrHasOrEndsWithFlag():Boolean[1] +{ + assert(meta::analytics::quality::model::domain::tests::NoModelQualityViolation.properties->filter(f| $f.name == 'validationFlag')->toOne()->meta::analytics::quality::model::domain::classBooleanPropertyShouldStartWithIsOrHasOrEndsWithFlag()->toOne().isValid); + assertFalse(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'myTestClassProperty')->toOne()->meta::analytics::quality::model::domain::classBooleanPropertyShouldStartWithIsOrHasOrEndsWithFlag()->toOne().isValid); +} + +function <> meta::analytics::quality::model::domain::tests::testClassPropertyEndingWithFlagShouldBeBoolean():Boolean[1] +{ + assert(meta::analytics::quality::model::domain::tests::NoModelQualityViolation.properties->filter(f| $f.name == 'validationFlag')->toOne()->meta::analytics::quality::model::domain::classPropertyEndingWithFlagShouldBeBoolean()->toOne().isValid); + assertFalse(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'myTestFlag')->toOne()->meta::analytics::quality::model::domain::classPropertyEndingWithFlagShouldBeBoolean()->toOne().isValid); +} + +function <> meta::analytics::quality::model::domain::tests::testClassPropertyWithToManyMultiplicityAreNamedCorrectly():Boolean[1] +{ + assert(meta::analytics::quality::model::domain::tests::NoModelQualityViolation.properties->filter(f| $f.name == 'validationFlags')->toOne()->meta::analytics::quality::model::domain::classPropertyWithToManyMultiplicityAreNamedCorrectly()->toOne().isValid); + assertFalse(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'myBadToManyProperty')->toOne()->meta::analytics::quality::model::domain::classPropertyWithToManyMultiplicityAreNamedCorrectly()->toOne().isValid); +} + +function <> meta::analytics::quality::model::domain::tests::testClassPropertyWithToOneMultiplicityAreNamedCorrectly():Boolean[1] +{ + assert(meta::analytics::quality::model::domain::tests::NoModelQualityViolation.properties->filter(f| $f.name == 'kerberos')->toOne()->meta::analytics::quality::model::domain::classPropertyWithToOneMultiplicityAreNamedCorrectly()->toOne().isValid); + assertFalse(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'myBadChildren')->toOne()->meta::analytics::quality::model::domain::classPropertyWithToOneMultiplicityAreNamedCorrectly()->toOne().isValid); + assertFalse(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'myBadSingletonProperties')->toOne()->meta::analytics::quality::model::domain::classPropertyWithToOneMultiplicityAreNamedCorrectly()->toOne().isValid); +} + +function <> meta::analytics::quality::model::domain::tests::testClassPropertyIntegersWithToOneMultiplicityAreNamedCorrectly():Boolean[1] +{ + assert(meta::analytics::quality::model::domain::tests::NoModelQualityViolation.properties->filter(f| $f.name == 'maturityYears')->toOne()->meta::analytics::quality::model::domain::classPropertyIntegersWithToOneMultiplicityAreNamedCorrectly()->toOne().isValid); + assertFalse(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'myInvalidDays')->toOne()->meta::analytics::quality::model::domain::classPropertyIntegersWithToOneMultiplicityAreNamedCorrectly()->toOne().isValid); +} + +function <> meta::analytics::quality::model::domain::tests::testClassPropertyStartingWithIsOrHasShouldBeBoolean():Boolean[1] +{ + assert(meta::analytics::quality::model::domain::tests::NoModelQualityViolation.properties->filter(f| $f.name == 'hasValidName')->toOne()->meta::analytics::quality::model::domain::classPropertyStartingWithIsOrHasShouldBeBoolean()->toOne().isValid); + assertFalse(meta::analytics::quality::model::domain::tests::myTestClass.properties->filter(f| $f.name == 'hasWrongType')->toOne()->meta::analytics::quality::model::domain::classPropertyStartingWithIsOrHasShouldBeBoolean()->toOne().isValid); +} + + +function <> meta::analytics::quality::model::domain::tests::runRulesForPackageableElementTest():Boolean[1] +{ + let results = [meta::analytics::quality::model::domain::tests::pack1,meta::analytics::quality::model::domain::tests::Employee]->meta::analytics::quality::model::domain::runQualityChecks(); + assertSize($results, 14); +} + + +function <> meta::analytics::quality::model::domain::tests::runAsHTMLForPackageableElementTest():Boolean[1] +{ + let results = meta::analytics::quality::model::domain::tests::pack1->meta::analytics::quality::model::domain::runChecksAsHtml([],true); + assertFalse($results->isEmpty()); +} + +function <> meta::analytics::quality::model::domain::tests::runRulesServiceTest():Boolean[1] +{ + let results = meta::analytics::quality::model::domain::findBugsReports('meta::analytics::quality::tests::pack1'); + assertFalse($results->isEmpty()); +} + + +function <> meta::analytics::quality::model::domain::tests::runRulesEmptyResults():Boolean[1] +{ + assert([]->meta::analytics::quality::model::domain::runRules()->isEmpty()); + let results = []->meta::analytics::quality::model::domain::runChecksAsHtml([],false); + println($results); + assertFalse($results->isEmpty()); +} + +function <> meta::analytics::quality::model::domain::tests::filterIgnoresTest():Boolean[1] +{ + let results = [meta::analytics::quality::model::domain::tests::testClassWithIgnoreFlags] + ->meta::analytics::quality::model::domain::runQualityChecks()->removeDuplicates() ->filter(bug|!$bug.detail.isValid); + + assertFalse($results->isEmpty()); + assertEquals(7,$results->size()); + + let filtered = $results->meta::analytics::quality::model::domain::filterInstancesToIgnore(); + + assertFalse($filtered->isEmpty()); + assertEquals(3,$filtered->size()); + +} + + +###Relational +Database meta::analytics::quality::model::domain::tests::TestDB +( + Table TABLE1( NAME VARCHAR(30) PRIMARY KEY) +) + +###Mapping + +Mapping meta::analytics::quality::model::domain::tests::TestMapping +( + meta::analytics::quality::model::domain::tests::myTestClass : Relational + { + Name: [meta::analytics::quality::model::domain::tests::TestDB]TABLE1.NAME + } +) + + +// ###Diagram + +// Diagram apps::pure::quality::tests::testDiagram(width=447.00000, height=164.00000) +// { +// TypeView NoModelQualityViolation_0_0_0_0_0_0_0_0_0_0( +// type=apps::pure::quality::tests::NoModelQualityViolation, +// stereotypesVisible=true, +// attributesVisible=true, +// attributeStereotypesVisible=true, +// attributeTypesVisible=true, +// color=#FFFFCC, +// lineWidth=1.0, +// position=(272.00000, 227.00000), +// width=159.00000, +// height=72.00000) + +// TypeView UntestedClass_1_1_1_1_1_1_1_1_1_1( +// type=apps::pure::quality::tests::UntestedClass, +// stereotypesVisible=true, +// attributesVisible=true, +// attributeStereotypesVisible=true, +// attributeTypesVisible=true, +// color=#FFFFCC, +// lineWidth=1.0, +// position=(270.00000, 343.00000), +// width=105.00000, +// height=48.00000) + +// TypeView Firm_2_2_2_2_2_2_2_2_2_2( +// type=apps::pure::quality::tests::Firm, +// stereotypesVisible=true, +// attributesVisible=true, +// attributeStereotypesVisible=true, +// attributeTypesVisible=true, +// color=#FFFFCC, +// lineWidth=1.0, +// position=(671.00000, 232.00000), +// width=46.00000, +// height=36.00000) + +// TypeView Employee_3_3_3_3_3_3_3_3( +// type=apps::pure::quality::tests::Employee, +// stereotypesVisible=true, +// attributesVisible=true, +// attributeStereotypesVisible=true, +// attributeTypesVisible=true, +// color=#FFFFCC, +// lineWidth=1.0, +// position=(484.00000, 240.00000), +// width=110.00000, +// height=48.00000) + + + +// PropertyView pview_4(property=apps::pure::quality::tests::Firm.employeesFlag, +// stereotypesVisible=true, +// nameVisible=true, +// color=#000000, +// lineWidth=-1.0, +// lineStyle=SIMPLE, +// points=[(694.00000,250.00000),(539.00000,264.00000)], +// label='', +// source=Firm_2_2_2_2_2_2_2_2_2_2, +// target=Employee_3_3_3_3_3_3_3_3, +// propertyPosition=(593.95000, 240.95242), +// multiplicityPosition=(593.95000, 223.10242)) + + + +// } diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/pom.xml b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/pom.xml new file mode 100644 index 00000000000..dbf6f592b64 --- /dev/null +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/pom.xml @@ -0,0 +1,22 @@ + + + org.finos.legend.engine + legend-engine-xts-analytics + 4.35.5-SNAPSHOT + + 4.0.0 + + Legend Engine - XTS - Analytics - Quality + legend-engine-xts-analytics-quality + pom + + + 11 + 11 + + + + legend-engine-xt-analytics-quality-pure + + + \ No newline at end of file diff --git a/legend-engine-xts-analytics/pom.xml b/legend-engine-xts-analytics/pom.xml index 9c5195afe99..e29bcd74b1c 100644 --- a/legend-engine-xts-analytics/pom.xml +++ b/legend-engine-xts-analytics/pom.xml @@ -36,5 +36,6 @@ legend-engine-xts-analytics-binding legend-engine-xts-analytics-search legend-engine-xts-analytics-store + legend-engine-xts-analytics-quality \ No newline at end of file diff --git a/pom.xml b/pom.xml index 864dee22b60..f3e44fe9af3 100644 --- a/pom.xml +++ b/pom.xml @@ -591,6 +591,11 @@ + + org.finos.legend.engine + legend-engine-xt-analytics-quality-pure + ${project.version} + org.finos.legend.engine legend-engine-xt-hostedService-pure From 751fdf86076041692de2a2081b3ab8e1c4836200 Mon Sep 17 00:00:00 2001 From: lisa kanbur Date: Tue, 28 Nov 2023 11:40:25 -0500 Subject: [PATCH 2/3] Revert a change --- .../src/main/resources/ideLightConfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light/src/main/resources/ideLightConfig.json b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light/src/main/resources/ideLightConfig.json index 52acbe688d9..4e3d9a0b71d 100644 --- a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light/src/main/resources/ideLightConfig.json +++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light/src/main/resources/ideLightConfig.json @@ -21,7 +21,7 @@ "connector": { "maxRequestHeaderSize": "32KiB", "type": "http", - "port": 9010 + "port": 9200 }, "requestLog": { "appenders": [ From 0134934db5597b57027f7d3a73c71beaf2137973 Mon Sep 17 00:00:00 2001 From: lisa kanbur Date: Wed, 29 Nov 2023 12:02:29 -0500 Subject: [PATCH 3/3] Remove reference to pure site --- .../main/resources/core_analytics_quality/checksEngine.pure | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/checksEngine.pure b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/checksEngine.pure index cc24c780113..0aedabd1ff6 100644 --- a/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/checksEngine.pure +++ b/legend-engine-xts-analytics/legend-engine-xts-analytics-quality/legend-engine-xt-analytics-quality-pure/src/main/resources/core_analytics_quality/checksEngine.pure @@ -220,7 +220,7 @@ function meta::analytics::quality::model::domain::runChecksAsHtml(pkgs:Packageab ('' + $bug.rule.description->makeString() + ''), ('' + $bug.detail.message->makeString() + ''), if($service - ,|('' + $peName + '') + ,|('' + $pe->cast(@PackageableElement)->fullPackageName('::') + '') ,|('' + 'toOne().source + '\",' + $bug.sourceInfo->toOne().startLine->toString() + ',' + $bug.sourceInfo->toOne().startColumn->toString() + ',false); return false;\'>' + $peName+ ''+ '') ), @@ -314,7 +314,7 @@ function <> meta::analytics::quality::model::domain::createRuleH ''+$type +''+$r.severity->toString() + '' + $r.category->toString() + '' + $r.description +'' + if($service - ,|('' + $pe->cast(@PackageableElement)->fullPackageName('::') + '') + ,|('' + $pe->cast(@PackageableElement)->fullPackageName('::') + '') ,|('' + 'toString() + ',' + $si.startColumn->toString() + ',false); return false;\'>' + $pe->cast(@PackageableElement)->fullPackageName('::') + ''+ '') );