Skip to content

Feature Finders

Each Analyzer is configured with a set of FeatureFinders that identify occurrences of features of interest within a unit of source-code.

A FeatureFinder can be any function with a name attribute that receives an object representing a unit of source-code (the type of the object will depend on the Analyzer being used) and returns a Feature.

Defining Feature Finders

The following utility functions can be used to define your own FeatureFinders.

For convenience, feature finding functions wrapped with these utilities you can return a dictionary with one of the following structures instead of a Feature object:

# A feature finder should typically return a list of occurrences of the
# feature in the given code_representation. Each occurrence should be a
# JSON-serializable dictionary that can capture whatever keys make sense
# for your feature.
{
    'occurrences': List[dict]
}
# Return an 'ignore' result in cases where the
# code_representation cannot be analyzed.
{
    'ignore': bool
}

codesurvey.analyzers.feature_finder(name)

Decorator for defining a named FeatureFinder.

Example usage:

@feature_finder('while')
def has_while(code_representation):
    if code_representation is None:
        return {'ignore': True}
    return {
        'occurrences': [
            {'character_index': match.start()}
            for match in re.finditer('test', str(code_representation))
        ]
    }
Source code in codesurvey/analyzers/features.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def feature_finder(name: str) -> Callable[[FeatureFinderFunction[CodeReprT]], FeatureFinder[CodeReprT]]:
    """Decorator for defining a named `FeatureFinder`.

    Example usage:

    ```python
    @feature_finder('while')
    def has_while(code_representation):
        if code_representation is None:
            return {'ignore': True}
        return {
            'occurrences': [
                {'character_index': match.start()}
                for match in re.finditer('test', str(code_representation))
            ]
        }
    ```

    """

    def decorator(func: FeatureFinderFunction[CodeReprT]) -> FeatureFinder[CodeReprT]:

        @wraps(func)
        def decorated(*args, **kwargs):
            feature = func(*args, **kwargs)
            return _normalize_feature(name=name, feature=feature)

        wrapped_feature_finder = cast(FeatureFinder[CodeReprT], decorated)
        wrapped_feature_finder.name = name
        return wrapped_feature_finder

    return decorator

codesurvey.analyzers.partial_feature_finder(name, feature_finder_function, *args, **kwargs)

Defines a FeatureFinder from the partial application of the given feature_finder_function with the given args and kwargs.

Example usage:

has_math_module = partial_feature_finder('math_module', module_feature_finder, module='math')
Source code in codesurvey/analyzers/features.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def partial_feature_finder(name: str,
                           feature_finder_function: Callable[..., Union[Feature, FeatureDict]],
                           *args, **kwargs) -> FeatureFinder[CodeReprT]:
    """Defines a FeatureFinder from the partial application of the given
    `feature_finder_function` with the given args and kwargs.

    Example usage:

    ```python
    has_math_module = partial_feature_finder('math_module', module_feature_finder, module='math')
    ```

    """
    return PartialFeatureFinder(
        name=name,
        feature_finder_function=feature_finder_function,
        args=args,
        kwargs=kwargs,
    )

codesurvey.analyzers.union_feature_finder(name, feature_finders)

Defines a FeatureFinder that returns the union of the occurrences of the given feature_finders.

The feature will only return an 'ignore' result if all feature_finders ignore the given source-code unit.

Example usage:

has_loop = union_feature_finder('loop', [has_for_loop, has_while_loop])
Source code in codesurvey/analyzers/features.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def union_feature_finder(name: str, feature_finders: Sequence[FeatureFinder[CodeReprT]]) -> FeatureFinder[CodeReprT]:
    """Defines a FeatureFinder that returns the union of the occurrences
    of the given `feature_finders`.

    The feature will only return an `'ignore'` result if all
    `feature_finders` ignore the given source-code unit.

    Example usage:

    ```python
    has_loop = union_feature_finder('loop', [has_for_loop, has_while_loop])
    ```

    """
    return partial_feature_finder(name, _union_feature_finder, feature_finders=feature_finders)

Core Classes

codesurvey.analyzers.FeatureFinder

Bases: Protocol, Generic[CodeReprInputT]

Callable for producing a Feature analysis for a given source-code unit representation.

Source code in codesurvey/analyzers/features.py
34
35
36
37
38
39
40
41
42
43
class FeatureFinder(Protocol, Generic[CodeReprInputT]):
    """Callable for producing a [`Feature`][codesurvey.analyzers.Feature]
    analysis for a given source-code unit representation."""

    name: str
    """Name of the feature that is analyzed."""

    def __call__(self, code_repr: CodeReprInputT) -> Feature:
        """Analyze the given source-code unit representation."""
        pass

name: str instance-attribute

Name of the feature that is analyzed.

__call__(code_repr: CodeReprInputT) -> Feature

Analyze the given source-code unit representation.

Source code in codesurvey/analyzers/features.py
41
42
43
def __call__(self, code_repr: CodeReprInputT) -> Feature:
    """Analyze the given source-code unit representation."""
    pass

codesurvey.analyzers.Feature dataclass

Analysis result of a single feature for a single source-code unit.

Source code in codesurvey/analyzers/features.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@dataclass
class Feature:
    """Analysis result of a single feature for a single source-code unit."""

    name: str
    """Name of the feature analyzed."""

    occurrences: Sequence[Dict[str, Any]] = field(default_factory=list)
    """Occurrences of the feature within the source-code unit.

    The data captured for each occurrence is determined by the
    [`FeatureFinder`][codesurvey.analyzers.FeatureFinder] that
    produced the Feature.

    """

    ignore: bool = False
    """If `True`, analysis of the feature was skipped for the source-code unit."""

name: str instance-attribute

Name of the feature analyzed.

occurrences: Sequence[Dict[str, Any]] = field(default_factory=list) class-attribute instance-attribute

Occurrences of the feature within the source-code unit.

The data captured for each occurrence is determined by the FeatureFinder that produced the Feature.

ignore: bool = False class-attribute instance-attribute

If True, analysis of the feature was skipped for the source-code unit.