Skip to content

Commit

Permalink
feat: resolve name paths in {@link } tags in documentation (#978)
Browse files Browse the repository at this point in the history
### Summary of Changes

`{@link }` tags can now also contain name paths to access instance &
static members of classes and variants of enums. Refer to the
documentation for usage instructions.
  • Loading branch information
lars-reimann authored Apr 3, 2024
1 parent 46b2bb2 commit b59d6f0
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 50 deletions.
24 changes: 24 additions & 0 deletions docs/language/common/comments.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,27 @@ an argument:
fun sum(a: Int, b: Int): sum: Int
```

To point to _static_ members of a [class][class] or an [enum variant][enum-variant] of an [enum][enum], write the name of
the containing declaration followed by a dot and the name of the member or enum variant:

```sds
/**
* To create a Table, use {@link Table.fromCsv}.
*/
class Table
```

To point to an _instance_ member of a [class][class], write the name of the containing declaration followed by a hash and
the name of the member:

```sds
/**
* An alias for {@link List#size}.
*/
fun size(list: List<Any?>): size: Int
```


#### `@param`

Use `@param` to document a [parameter][parameter] of a callable declaration. This tag takes the name of the parameter
Expand Down Expand Up @@ -155,6 +176,9 @@ fun sum(a: Int, b: Int): sum: Int
the [`@param`](#param), [`@result`](#result), and [`@typeParam`](#typeparam) tags on the containing declaration,
respectively.

[class]: ../stub-language/classes.md
[enum]: ../stub-language/enumerations.md
[enum-variant]: ../stub-language/enumerations.md#enum-variants
[parameter]: parameters.md
[result]: results.md
[type-parameter]: ../stub-language/type-parameters.md
6 changes: 3 additions & 3 deletions docs/stdlib/safeds/data/tabular/containers/Column.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ A column is a named collection of values.
@Pure
fun transform<R>(
transformer: (param1: T) -> param2: R
) -> result1: Column
) -> result1: Column<R>

/**
* Calculate Pearson correlation between this and another column. Both columns have to be numerical.
Expand Down Expand Up @@ -738,7 +738,7 @@ The original column is not modified.

| Name | Type | Description |
|------|------|-------------|
| `result1` | [`Column<Any?>`][safeds.data.tabular.containers.Column] | The transformed column. |
| `result1` | [`Column<R>`][safeds.data.tabular.containers.Column] | The transformed column. |

**Type parameters:**

Expand All @@ -752,7 +752,7 @@ The original column is not modified.
@Pure
fun transform<R>(
transformer: (param1: T) -> param2: R
) -> result1: Column
) -> result1: Column<R>
```

## `#!sds fun` variance {#safeds.data.tabular.containers.Column.variance data-toc-label='variance'}
Expand Down
18 changes: 9 additions & 9 deletions docs/stdlib/safeds/data/tabular/containers/Table.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ A table is a two-dimensional collection of data. It can either be seen as a list

To create a `Table` call the constructor or use one of the following static methods:

| Method | Description |
| ------------------------------------------------------------------| -------------------------------------- |
| [fromCsvFile][safeds.data.tabular.containers.Table.fromCsvFile] | Create a table from a CSV file. |
| [fromJsonFile][safeds.data.tabular.containers.Table.fromJsonFile] | Create a table from a JSON file. |
| [fromDict][safeds.data.tabular.containers.Table.fromDict] | Create a table from a dictionary. |
| [fromColumns][safeds.data.tabular.containers.Table.fromColumns] | Create a table from a list of columns. |
| [fromRows][safeds.data.tabular.containers.Table.fromRows] | Create a table from a list of rows. |
| Method | Description |
| ---------------------------| -------------------------------------- |
| [Table.fromCsvFile][safeds.data.tabular.containers.Table.fromCsvFile] | Create a table from a CSV file. |
| [Table.fromJsonFile][safeds.data.tabular.containers.Table.fromJsonFile] | Create a table from a JSON file. |
| [Table.fromDict][safeds.data.tabular.containers.Table.fromDict] | Create a table from a dictionary. |
| [Table.fromColumns][safeds.data.tabular.containers.Table.fromColumns] | Create a table from a list of columns. |
| [Table.fromRows][safeds.data.tabular.containers.Table.fromRows] | Create a table from a list of rows. |

Note: When removing the last column of the table, the `number_of_columns` property will be set to 0.

Expand Down Expand Up @@ -61,7 +61,7 @@ Note: When removing the last column of the table, the `number_of_columns` proper
/**
* Read data from an Excel file into a table.
*
* Valid file extensions are `.xls`, '.xlsx', `.xlsm`, `.xlsb`, `.odf`, `.ods` and `.odt`.
* Valid file extensions are `.xls`, `.xlsx`, `.xlsm`, `.xlsb`, `.odf`, `.ods` and `.odt`.
*
* @param path The path to the Excel file.
*
Expand Down Expand Up @@ -1940,7 +1940,7 @@ Create a table from a dictionary that maps column names to column values.

Read data from an Excel file into a table.

Valid file extensions are `.xls`, '.xlsx', `.xlsm`, `.xlsb`, `.odf`, `.ods` and `.odt`.
Valid file extensions are `.xls`, `.xlsx`, `.xlsm`, `.xlsb`, `.odf`, `.ods` and `.odt`.

**Parameters:**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import {
JSDocElement,
JSDocRenderOptions,
type JSDocTag,
NameProvider,
parseJSDoc,
} from 'langium';
import {
isSdsCallable,
isSdsClass,
isSdsDeclaration,
isSdsEnum,
isSdsParameter,
isSdsResult,
isSdsTypeParameter,
Expand All @@ -21,13 +24,23 @@ import {
SdsTypeParameter,
} from '../generated/ast.js';
import { isEmpty } from '../../helpers/collections.js';
import { SafeDsServices } from '../safe-ds-module.js';
import { getClassMembers, getEnumVariants, isStatic } from '../helpers/nodeProperties.js';

const PARAM_TAG = 'param';
const RESULT_TAG = 'result';
const SINCE_TAG = 'since';
const TYPE_PARAM_TAG = 'typeParam';

export class SafeDsDocumentationProvider extends JSDocDocumentationProvider {
private readonly nameProvider: NameProvider;

constructor(services: SafeDsServices) {
super(services);

this.nameProvider = services.references.NameProvider;
}

/**
* Returns the documentation of the given node as a Markdown string.
*/
Expand Down Expand Up @@ -148,23 +161,112 @@ export class SafeDsDocumentationProvider extends JSDocDocumentationProvider {
return super.documentationTagRenderer(node, tag);
}
},
renderLink: (name, display) => {
if (!linkRenderer) {
return super.documentationLinkRenderer(node, name, display);
renderLink: (namePath, display) => {
const target = this.findTarget(node, namePath);
if (!target) {
return display;
}

const description =
this.findNameInPrecomputedScopes(node, name) ?? this.findNameInGlobalScope(node, name);
const target = description?.node;
if (!linkRenderer) {
const nameSegment = this.nameProvider.getNameNode(target);
if (!nameSegment) {
/* c8 ignore next 2 */
return display;
}

if (isSdsDeclaration(target)) {
return linkRenderer(target, display);
} else {
return linkRenderer(undefined, display);
const line = nameSegment.range.start.line + 1;
const character = nameSegment.range.start.character + 1;
const uri = AstUtils.getDocument(target).uri.with({ fragment: `L${line},${character}` });
return `[${display}](${uri.toString()})`;
}

return linkRenderer(target, display);
},
};
}

private findTarget(node: AstNode, namePath: string): SdsDeclaration | undefined {
let [globalName, ...rest] = this.tokenizeNamepath(namePath);
// `rest` contains pairs of separators and names
if (!globalName || rest.length % 2 !== 0) {
/* c8 ignore next 2 */
return undefined;
}

let current = this.findGlobalDeclaration(node, globalName);

while (current && !isEmpty(rest)) {
const [separator, name] = rest.slice(0, 2);
rest = rest.slice(2);

if (separator === '.') {
current = this.findStaticMember(current, name);
} else if (separator === '#') {
current = this.findInstanceMember(current, name);
} else {
/* c8 ignore next 2 */
return undefined;
}
}

return current;
}

private tokenizeNamepath(namePath: string): string[] {
const result = [];
let current = '';

for (const c of namePath) {
if (c === '.' || c === '#') {
result.push(current, c);
current = '';
} else {
current += c;
}
}

result.push(current);
return result;
}

private findGlobalDeclaration(node: AstNode, name: string): SdsDeclaration | undefined {
const description = this.findNameInPrecomputedScopes(node, name) ?? this.findNameInGlobalScope(node, name);
const target = description?.node;

if (isSdsDeclaration(target)) {
return target;
} else {
return undefined;
}
}

private findStaticMember(node: SdsDeclaration, name: string | undefined): SdsDeclaration | undefined {
if (!name) {
/* c8 ignore next 2 */
return undefined;
}

if (isSdsClass(node)) {
return getClassMembers(node).find((it) => isStatic(it) && it.name === name);
} else if (isSdsEnum(node)) {
return getEnumVariants(node).find((it) => it.name === name);
} else {
return undefined;
}
}

private findInstanceMember(node: SdsDeclaration, name: string | undefined): SdsDeclaration | undefined {
if (!name) {
/* c8 ignore next 2 */
return undefined;
}

if (isSdsClass(node)) {
return getClassMembers(node).find((it) => !isStatic(it) && it.name === name);
} else {
return undefined;
}
}
}

const isBlockTag = (element: JSDocElement): element is JSDocTag => {
Expand All @@ -175,4 +277,4 @@ const isTag = (element: JSDocElement): element is JSDocTag => {
return 'name' in element;
};

type LinkRenderer = (target: SdsDeclaration | undefined, display: string) => string | undefined;
type LinkRenderer = (target: SdsDeclaration, display: string) => string | undefined;
Original file line number Diff line number Diff line change
Expand Up @@ -426,11 +426,7 @@ export class SafeDsMarkdownGenerator {

private renderDescription(node: SdsDeclaration) {
return this.documentationProvider.getDescription(node, (target, display) => {
if (target) {
return `[${display}][${getQualifiedName(target)}]`;
} else {
return display;
}
return `[${display}][${getQualifiedName(target)}]`;
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class Column<T = Any?>(
@Pure
fun transform<R>(
transformer: (param1: T) -> param2: R
) -> result1: Column
) -> result1: Column<R>

/**
* Calculate Pearson correlation between this and another column. Both columns have to be numerical.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ from safeds.data.tabular.typing import ColumnType, Schema
*
* To create a `Table` call the constructor or use one of the following static methods:
*
* | Method | Description |
* | ------------------------------------------------------------------| -------------------------------------- |
* | [fromCsvFile][safeds.data.tabular.containers.Table.fromCsvFile] | Create a table from a CSV file. |
* | [fromJsonFile][safeds.data.tabular.containers.Table.fromJsonFile] | Create a table from a JSON file. |
* | [fromDict][safeds.data.tabular.containers.Table.fromDict] | Create a table from a dictionary. |
* | [fromColumns][safeds.data.tabular.containers.Table.fromColumns] | Create a table from a list of columns. |
* | [fromRows][safeds.data.tabular.containers.Table.fromRows] | Create a table from a list of rows. |
* | Method | Description |
* | ---------------------------| -------------------------------------- |
* | {@link Table.fromCsvFile} | Create a table from a CSV file. |
* | {@link Table.fromJsonFile} | Create a table from a JSON file. |
* | {@link Table.fromDict} | Create a table from a dictionary. |
* | {@link Table.fromColumns} | Create a table from a list of columns. |
* | {@link Table.fromRows} | Create a table from a list of rows. |
*
* Note: When removing the last column of the table, the `number_of_columns` property will be set to 0.
*
Expand Down Expand Up @@ -60,7 +60,7 @@ class Table(
/**
* Read data from an Excel file into a table.
*
* Valid file extensions are `.xls`, '.xlsx', `.xlsm`, `.xlsb`, `.odf`, `.ods` and `.odt`.
* Valid file extensions are `.xls`, `.xlsx`, `.xlsm`, `.xlsb`, `.odf`, `.ods` and `.odt`.
*
* @param path The path to the Excel file.
*
Expand Down
Loading

0 comments on commit b59d6f0

Please sign in to comment.