in packages/lu/src/parser/lufile/parseFileContents.js [538:672]
const parseFeatureSections = function(parsedContent, featuresToProcess, config) {
if (!config.enableFeatures) {
throwDiagnosticError({
message: 'Do not support Features. Please make sure enableFeatures is set to true.',
});
}
// We are only interested in extracting features and setting things up here.
(featuresToProcess || []).forEach(section => {
if (section.Type === INTENTTYPE) {
// Intents can only have features and nothing else.
if (section.Roles) {
let errorMsg = `Intents can only have usesFeature and nothing else. Invalid definition for "${section.Name}".`;
throwDiagnosticError({
message: errorMsg,
range: section.Range
});
}
if (!section.Features) return;
// verify intent exists
section.Name = section.Name.replace(/[\'\"]/g, "");
let intentExists = parsedContent.LUISJsonStructure.intents.find(item => item.name === section.Name);
if (intentExists !== undefined) {
// verify the list of features requested have all been defined.
let featuresList = section.Features.split(/[,;]/g).map(item => item.trim().replace(/^[\'\"]|[\'\"]$/g, ""));
let featuresVisited = new Set();
(featuresList || []).forEach(feature => {
// usually phraseList has higher priority when searching the existing entities as phraseList is more likely to be used as feature
// but when a ml entity and a phraseList have same name, this assumption will cause a problem in situation that
// users actually want to specify the ml entity as feature rather than phraseList
// currently we can not distinguish them in lu file, as for @ intent A usesFeature B, B can be a ml entity or a phraseList with same name
// the lu format for usesFeatures defintion need to be updated if we want to resolve this confusion completely
let entityExists = (parsedContent.LUISJsonStructure.flatListOfEntityAndRoles || []).find(item => (item.name == feature || item.name == `${feature}(interchangeable)`) && item.type == EntityTypeEnum.PHRASELIST);
// if phraseList is not matched to the feature, search other non phraseList entities
// or this intent use multiple features with same name, e.g., @ intent A usesFeatures B, B.
// and current loop is processiong the second feature B
// this is allowed in luis portal, you can add ml entity and phraseList features of same name to an intent
// the exported intent useFeatures will have two features with same name listed, just like above sample @ intent A usesFeatures B, B
if (!entityExists || featuresVisited.has(feature)) {
entityExists = (parsedContent.LUISJsonStructure.flatListOfEntityAndRoles || []).find(item => item.name == feature && item.type !== EntityTypeEnum.PHRASELIST);
}
if (!featuresVisited.has(feature)) featuresVisited.add(feature);
let featureIntentExists = (parsedContent.LUISJsonStructure.intents || []).find(item => item.name == feature);
if (entityExists) {
if (entityExists.type === EntityTypeEnum.PHRASELIST) {
// de-dupe and add features to intent.
validateFeatureAssignment(section.Type, section.Name, entityExists.type, feature, section.Range);
addFeatures(intentExists, feature, featureTypeEnum.featureToModel, section.Range, featureProperties.phraseListFeature, true);
// set enabledForAllModels on this phrase list
let plEnity = parsedContent.LUISJsonStructure.model_features.find(item => item.name == feature);
if (plEnity.enabledForAllModels === undefined) plEnity.enabledForAllModels = false;
} else {
// de-dupe and add model to intent.
validateFeatureAssignment(section.Type, section.Name, entityExists.type, feature, section.Range);
addFeatures(intentExists, feature, featureTypeEnum.modelToFeature, section.Range, featureProperties.entityFeatureToModel[entityExists.type]);
}
} else if (featureIntentExists) {
// Add intent as a feature to another intent
validateFeatureAssignment(section.Type, section.Name, INTENTTYPE, feature, section.Range);
addFeatures(intentExists, feature, featureTypeEnum.modelToFeature, section.Range, featureProperties.intentFeatureToModel);
} else {
// Item must be defined before being added as a feature.
let errorMsg = `Features must be defined before assigned to an intent. No definition found for feature "${feature}" in usesFeature definition for intent "${section.Name}"`;
throwDiagnosticError({
message: errorMsg,
range: section.Range
});
}
})
} else {
let errorMsg = `Features can only be added to intents that have a definition. Invalid feature definition found for intent "${section.Name}".`;
throwDiagnosticError({
message: errorMsg,
range: section.Range
});
}
} else {
// handle as entity
if (section.Features) {
let featuresList = section.Features.split(/[,;]/g).map(item => item.trim());
// Find the source entity from the collection and get its type
let srcEntityInFlatList = (parsedContent.LUISJsonStructure.flatListOfEntityAndRoles || []).find(item => item.name == section.Name);
let entityType = srcEntityInFlatList ? srcEntityInFlatList.type : undefined;
let featuresVisited = new Set();
(featuresList || []).forEach(feature => {
feature = feature.replace(/[\'\"]/g, "");
// usually phraseList has higher priority when searching the existing entities as phraseList is more likely to be used as feature to a entity
// but when a ml entity and a phraseList have same name, this assumption will cause a problem in situation that
// users actually want to specify the ml entity as feature rather than phraseList
// currently we can not distinguish them in lu file, as for @ ml A usesFeature B, B can be another ml entity or a phraseList with same name
// the lu format for usesFeatures defintion need to be updated if we want to resolve this confusion completely
let featureExists = (parsedContent.LUISJsonStructure.flatListOfEntityAndRoles || []).find(item => (item.name == feature || item.name == `${feature}(interchangeable)`) && item.type == EntityTypeEnum.PHRASELIST);
// if phraseList is not matched to the feature, search other non phraseList entities
// or this intent use multiple features with same name, e.g., @ intent A usesFeatures B, B.
// and current loop is processiong the second feature B
// this is allowed in luis portal, you can add ml entity and phraseList features of same name to an intent
// the exported intent useFeatures will have two features with same name listed, just like above sample @ intent A usesFeatures B, B
if (!featureExists || featuresVisited.has(feature)) {
featureExists = (parsedContent.LUISJsonStructure.flatListOfEntityAndRoles || []).find(item => item.name == feature && item.type !== EntityTypeEnum.PHRASELIST);
}
if (!featuresVisited.has(feature)) featuresVisited.add(feature);
let featureIntentExists = (parsedContent.LUISJsonStructure.intents || []).find(item => item.name == feature);
// find the entity based on its type.
let srcEntity = (parsedContent.LUISJsonStructure[luisEntityTypeMap[entityType]] || []).find(item => item.name == section.Name);
if (featureExists) {
if (featureExists.type === EntityTypeEnum.PHRASELIST) {
// de-dupe and add features to intent.
validateFeatureAssignment(entityType, section.Name, featureExists.type, feature, section.Range);
addFeatures(srcEntity, feature, featureTypeEnum.featureToModel, section.Range, featureProperties.phraseListFeature, true);
// set enabledForAllModels on this phrase list
let plEnity = parsedContent.LUISJsonStructure.model_features.find(item => item.name == feature);
if (plEnity.enabledForAllModels === undefined) plEnity.enabledForAllModels = false;
} else {
// de-dupe and add model to intent.
validateFeatureAssignment(entityType, section.Name, featureExists.type, feature, section.Range);
addFeatures(srcEntity, feature, featureTypeEnum.modelToFeature, section.Range, featureProperties.entityFeatureToModel[featureExists.type]);
}
} else if (featureIntentExists) {
// Add intent as a feature to another intent
validateFeatureAssignment(entityType, section.Name, INTENTTYPE, feature, section.Range);
addFeatures(srcEntity, feature, featureTypeEnum.modelToFeature, section.Range, featureProperties.intentFeatureToModel);
} else {
addFeatures(srcEntity, feature, featureTypeEnum.modelToFeature, section.Range, featureProperties.intentFeatureToModel);
}
});
}
}
});
// Circular dependency for features is not allowed. E.g. A usesFeature B usesFeature A is not valid.
verifyNoCircularDependencyForFeatures(parsedContent);
}