Skip to content

Commit

Permalink
v0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
KamasamaK committed Nov 29, 2017
1 parent 84f05c2 commit 0abbb6b
Show file tree
Hide file tree
Showing 39 changed files with 999 additions and 429 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,3 +1,4 @@
.vscode/cSpell.json
node_modules/
out/
npm-debug.log
11 changes: 11 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,17 @@

All notable changes to the CFML extension will be documented in this file.

## [0.2.0] - 2017-11-29

- Added global definition filtering based on engine
- Improved type inference
- Changed signature format
- Argument type now indicates component name
- Improved syntax highlighting for properties
- Now able to ignore CFML comments
- Added variables assigned from tag attributes
- Added option to tsconfig

## [0.1.4] - 2017-11-13

- Added `cfcatch` help
Expand Down
15 changes: 7 additions & 8 deletions README.md
Expand Up @@ -49,12 +49,12 @@ _Peek Definition:_ Win/Linux: `Alt+F12` (`Ctrl`+hover provides a smaller, altern

The following are the configurable Settings (Win/Linux: `Ctrl+Comma`; Mac: `Cmd+Comma`) that this extension contributes to VS Code:

- `cfml.globalDefinitions.source`: The source of the global definitions. Currently only supports CFDocs. [*Default*: `cfdocs`].
- `cfml.cfDocs.localPath`: [*Optional*] Absolute path to the data directory of CFDocs.
- `cfml.globalDefinitions.source`: The source of the global definitions. Currently only supports CFDocs. [*Default*: `cfdocs`]
- `cfml.cfDocs.source`: Indicates which source will be used for CFDocs.
**Values**
- `remote`: Retrieve resources remotely from GitHub. [*Default*]
- `local`: Retrieve resources locally using `cfml.cfDocs.localPath`.
- `cfml.cfDocs.localPath`: [*Optional*] Absolute path to the data directory of CFDocs.
- `cfml.hover.enable`: Whether hover is enabled. [*Default*: `true`]
- `cfml.signature.enable`: Whether signature help is enabled. [*Default*: `true`]
- `cfml.suggest.enable`: Whether completion help is enabled. [*Default*: `true`]
Expand All @@ -81,6 +81,8 @@ The following are the configurable Settings (Win/Linux: `Ctrl+Comma`; Mac: `Cmd+
}
]
```
- `cfml.engine.name`: Name of the CFML engine against which to filter.
- `cfml.engine.version`: Version of the CFML engine against which to filter. SemVer format is preferred.

## Commands

Expand All @@ -95,28 +97,25 @@ Used in Command Palette (Win/Linux: `Ctrl+Shift+P`; Mac: `Cmd+Shift+P`). Can als

1. As currently implemented, there is no embedded language support. Unfortunately, VS Code does not currently seem to have this native capability. This means that you will not get robust HTML/CSS/JS assistance within CFML files. You will still get syntax highlighting and you can use user snippets and Emmet to supplement somewhat.
1. An extension of the issue above is that as implemented there is only one language defined for CFML. This can cause a number of issues where functionality or settings are based on language ID. For example, with Emmet enabled, you will get the Emmet functionality throughout the CFML files and contexts.
1. Removing, moving, or renaming folders does not update the workspace definitions or symbols and will cause discrepancies with those resources. For now, you can use the command to refresh workspace definitions at any time.
1. Removing, moving, or renaming folders does not update the workspace definitions or symbols and will cause discrepancies with those resources. To rectify this, you may use the command to refresh workspace definitions at any time.
1. Completion suggestions are not always properly contextual.
1. The "parsing" is done with regular expressions without considering context in most cases, which means you will get non-CFML being parsed as CFML. This also means that strings and comments will often be parsed as if they were part of code.
1. The "parsing" is mostly done with regular expressions without considering context in most cases, which can result in occasional issues. One way this manifests is that you may get non-CFML being parsed as CFML. This can also result in strings and comments being parsed as if they were part of code. To simplify the expressions, semicolons are expected as terminators in CFScript even though they are optional in some engines.

## Future Plans

Feel free to open issues for these or any other features you would find helpful so we can discuss.

- Filter global functions and tags based on engine setting
- Signature help for external component functions
- Hover documentation for external component functions
- Provide additional completion suggestions
- Enumerated values
- Script-based tag functions
- Script-based tag attribute names
- External component functions
- Component paths
- Global member functions
- Enumerated values
- Definitions for more contexts
- Type Definitions
- References
- Add additional formats to supported global definitions source (e.g. CFBuilder)
- Custom mapping
- Use proper parser ([CFParser](https://github.com/cfparser/cfparser))
- Utilize a CFML language server
Expand Down
57 changes: 19 additions & 38 deletions package.json
Expand Up @@ -2,13 +2,13 @@
"name": "vscode-cfml",
"displayName": "CFML",
"description": "CFML language",
"version": "0.1.4",
"version": "0.2.0",
"preview": true,
"author": "KamasamaK",
"publisher": "KamasamaK",
"license": "MIT",
"engines": {
"vscode": "^1.17.0"
"vscode": "^1.18.0"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -190,39 +190,20 @@
},
"description": "Extra tags you wish to include in every DocBlock"
},
"cfml.engine": {
"type": [
"object",
"null"
],
"properties": {
"name": {
"type": "string",
"description": "Engine name.",
"default": "coldfusion",
"enum": [
"coldfusion",
"lucee",
"railo",
"openbd"
]
},
"version": {
"type": "string",
"description": "Engine version.",
"default": "2016",
"enum": [
"9",
"10",
"11",
"2016",
"4.5",
"5"
]
}
},
"description": "CFML engine to validate against. Currently ignored.",
"default": null
"cfml.engine.name": {
"type": "string",
"description": "Name of the CFML engine against which to filter.",
"enum": [
"coldfusion",
"lucee",
"railo",
"openbd"
]
},
"cfml.engine.version": {
"type": "string",
"pattern": "^((0|[1-9]\\d*)(\\.(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))?)?)?$",
"description": "Version of the CFML engine against which to filter. SemVer format is preferred."
}
}
},
Expand Down Expand Up @@ -276,11 +257,11 @@
"semver": "^5.4.1"
},
"devDependencies": {
"typescript": "^2.6.1",
"typescript": "^2.6.2",
"tslint": "^5.8.0",
"vscode": "^1.1.6",
"vscode": "^1.1.8",
"mocha": "^4.0.1",
"@types/node": "^8.0.51",
"@types/node": "^8.0.53",
"@types/mocha": "^2.2.44"
}
}
8 changes: 6 additions & 2 deletions resources/schemas/cfdocs.schema.json
Expand Up @@ -8,18 +8,21 @@
"minimum_version": {
"title": "Minimum version",
"description": "The version of the engine in which this was introduced",
"type": "string"
"type": "string",
"pattern": "^((0|[1-9]\\d*)(\\.(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))?)?)?$"
},
"deprecated": {
"title": "Deprecated version",
"description": "The version of the engine in which this was deprecated",
"type": "string",
"pattern": "^((0|[1-9]\\d*)(\\.(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))?)?)?$",
"default": ""
},
"removed": {
"title": "Removed version",
"description": "The version of the engine in which this was removed",
"type": "string",
"pattern": "^((0|[1-9]\\d*)(\\.(0|[1-9]\\d*)(\\.(0|[1-9]\\d*))?)?)?$",
"default": ""
},
"notes": {
Expand Down Expand Up @@ -166,7 +169,8 @@
"name": {
"title": "Name",
"description": "The name of the tag or function. Use lowercase.",
"type": "string"
"type": "string",
"pattern": "^[a-z]\\w+$"
},
"description": {
"title": "Description",
Expand Down
11 changes: 8 additions & 3 deletions src/cfmlMain.ts
@@ -1,5 +1,5 @@
import {
commands, languages, TextDocument, ConfigurationTarget, ExtensionContext, WorkspaceConfiguration, workspace, Uri, FileSystemWatcher, extensions, IndentAction
commands, languages, TextDocument, ConfigurationTarget, ExtensionContext, WorkspaceConfiguration, workspace, Uri, FileSystemWatcher, extensions, IndentAction, ConfigurationChangeEvent
} from "vscode";
import CFMLHoverProvider from "./features/hoverProvider";
import CFMLDocumentSymbolProvider from "./features/documentSymbolProvider";
Expand All @@ -15,7 +15,6 @@ import { COMPONENT_FILE_GLOB, parseComponent } from "./entities/component";
import { cacheComponent, clearCachedComponent } from "./features/cachedEntities";
import CFMLDefinitionProvider from "./features/definitionProvider";
import * as fs from "fs";
import * as path from "path";
import DocBlockCompletions from "./features/docBlocker/docCompletionProvider";

export const LANGUAGE_ID = "cfml";
Expand Down Expand Up @@ -123,13 +122,19 @@ export function activate(context: ExtensionContext): void {
clearCachedComponent(componentUri);
});

context.subscriptions.push(workspace.onDidChangeConfiguration((evt: ConfigurationChangeEvent) => {
if (evt.affectsConfiguration("cfml.globalDefinitions") || evt.affectsConfiguration("cfml.cfDocs") || evt.affectsConfiguration("cfml.engine")) {
commands.executeCommand("cfml.refreshGlobalDefinitionCache");
}
}));

const cfmlSettings: WorkspaceConfiguration = workspace.getConfiguration("cfml");
const autoCloseTagExt = extensions.getExtension("formulahendry.auto-close-tag");
if (autoCloseTagExt) {
const autoCloseTagsSettings: WorkspaceConfiguration = workspace.getConfiguration("auto-close-tag");
const autoCloseLanguages = autoCloseTagsSettings.get<string[]>("activationOnLanguage");
const autoCloseExcludedTags = autoCloseTagsSettings.get<string[]>("excludedTags");
const enableAutoCloseTags = cfmlSettings.get<boolean>("autoCloseTags.enable");
// const enableAutoCloseTags = cfmlSettings.get<boolean>("autoCloseTags.enable");
if (cfmlSettings.get<boolean>("autoCloseTags.enable")) {
if (!autoCloseLanguages.includes(LANGUAGE_ID)) {
autoCloseLanguages.push(LANGUAGE_ID);
Expand Down
2 changes: 1 addition & 1 deletion src/entities/attribute.ts
@@ -1,7 +1,7 @@
import { Range, TextDocument } from "vscode";
import { MyMap, MySet } from "../utils/collections";

export const ATTRIBUTES_PATTERN = /\b(\w+)\b(?:(\s*=\s*)(?:(['"])(.*?)\3|([\w$:.]+)))?/gi;
const ATTRIBUTES_PATTERN = /\b(\w+)\b(?:(\s*=\s*)(?:(['"])(.*?)\3|([\w$:.]+)))?/gi;
export const VALUE_PATTERN = /=\s*(?:(['"])?(?:(?!\1).)*|[^\s]*)$/;

export interface Attribute {
Expand Down
12 changes: 11 additions & 1 deletion src/entities/cfcatch.ts
@@ -1,4 +1,12 @@
export const cfcatchVariables = {
import { CompletionEntry } from "../features/completionItemProvider";

export interface CfcatchPropertyDetails extends CompletionEntry { }

export interface CfcatchProperties {
[propertyName: string]: CfcatchPropertyDetails;
}

export const cfcatchProperties: CfcatchProperties = {
"type": {
detail: "cfcatch.type",
description: "Type: Exception type, as specified in cfcatch."
Expand Down Expand Up @@ -60,3 +68,5 @@ export const cfcatchVariables = {
description: "Applies to type=\"application\" and \"custom\". Custom error message; information that the default exception handler does not display."
},
};

export const cfcatchPropertyPrefixPattern: RegExp = /\bcfcatch\s*(?:\.\s*|\[\s*['"])$/;
38 changes: 19 additions & 19 deletions src/entities/component.ts
@@ -1,13 +1,13 @@
import { Uri, TextDocument, workspace, Position, Range, Location } from "vscode";
import { Uri, TextDocument, workspace, Position, Range, Location, WorkspaceFolder } from "vscode";
import { UserFunction, ComponentFunctions, parseScriptFunctions, parseTagFunctions, UserFunctionSignature } from "./userFunction";
import { Function } from "./function";
import { DataType } from "./dataType";
import * as path from "path";
import * as fs from "fs";
import * as cachedEntities from "../features/cachedEntities";
import { Property, parseProperties, Properties } from "./property";
import { parseVariables, Variable } from "./variable";
import { parseDocBlock, DocBlockKeyValue, getKeyPattern } from "./docblock";
import { parseProperties, Properties } from "./property";
import { parseVariableAssignments, Variable } from "./variable";
import { parseDocBlock, DocBlockKeyValue } from "./docblock";
import { parseAttributes, Attributes, Attribute } from "./attribute";
import { MyMap, MySet } from "../utils/collections";

Expand All @@ -24,12 +24,12 @@ export interface ReferencePattern {
export const objectReferencePatterns: ReferencePattern[] = [
// new object
{
pattern: /new\s+(['"])?([^(\1]+?)\1\(/gi,
pattern: /new\s+(['"])?([^('"]+?)\1\(/gi,
refIndex: 2
},
// createObject
{
pattern: /createObject\s*\(\s*(['"])component\1\s*,\s*(['"])([^\2]+?)\2/gi,
pattern: /createObject\s*\(\s*(['"])component\1\s*,\s*(['"])([^'"]+?)\2/gi,
refIndex: 3
},
// cfobject or cfinvoke
Expand Down Expand Up @@ -200,7 +200,7 @@ export function parseComponent(document: TextDocument): Component {
document.positionAt(componentMatch.index + 3 + componentDoc.length)
)
);
const docBlockAttributes = processDocBlock(parsedDocBlock, document);
const docBlockAttributes: ComponentAttributes = processDocBlock(parsedDocBlock);
Object.assign(componentAttributes, docBlockAttributes);

parsedDocBlock.filter((docAttribute: DocBlockKeyValue) => {
Expand All @@ -215,15 +215,15 @@ export function parseComponent(document: TextDocument): Component {
}

if (componentAttr) {
const componentAttributePrefixOffset = componentMatch.index + attributePrefix.length;
const componentAttributePrefixOffset: number = componentMatch.index + attributePrefix.length;
const componentAttributeRange = new Range(
document.positionAt(componentAttributePrefixOffset),
document.positionAt(componentAttributePrefixOffset + componentAttr.length)
);

const parsedAttributes: Attributes = parseAttributes(document, componentAttributeRange, componentAttributeNames);

const tagAttributes = processAttributes(parsedAttributes);
const tagAttributes: ComponentAttributes = processAttributes(parsedAttributes);
Object.assign(componentAttributes, tagAttributes);

if (parsedAttributes.has("extends")) {
Expand All @@ -242,7 +242,7 @@ export function parseComponent(document: TextDocument): Component {
component.extends = componentPathToUri(componentAttributes.extends, document.uri);
} else if (propName === "implements") {
componentAttributes.implements.split(",").forEach((element: string) => {
const implementsUri = componentPathToUri(element.trim(), document.uri);
const implementsUri: Uri = componentPathToUri(element.trim(), document.uri);
if (implementsUri) {
if (!component.implements) {
component.implements = [];
Expand All @@ -261,7 +261,7 @@ export function parseComponent(document: TextDocument): Component {
let componentFunctions = new ComponentFunctions();
let userFunctions: UserFunction[] = parseScriptFunctions(document);
userFunctions = userFunctions.concat(parseTagFunctions(document));
let earliestFunctionRangeStart = document.positionAt(documentText.length);
let earliestFunctionRangeStart: Position = document.positionAt(documentText.length);
userFunctions.forEach((compFun: UserFunction) => {
if (compFun.location.range.start.isBefore(earliestFunctionRangeStart)) {
earliestFunctionRangeStart = compFun.location.range.start;
Expand All @@ -274,7 +274,7 @@ export function parseComponent(document: TextDocument): Component {
// TODO: ImplicitFunctions

const componentDefinitionRange = new Range(document.positionAt(head.length), earliestFunctionRangeStart);
component.variables = parseVariables(document, componentIsScript, componentDefinitionRange);
component.variables = parseVariableAssignments(document, componentIsScript, componentDefinitionRange);

return component;
}
Expand All @@ -286,7 +286,7 @@ export function parseComponent(document: TextDocument): Component {
* Parses a documentation block for a component and returns an object conforming to the ComponentAttributes interface
* @param docBlock The documentation block to be processed
*/
function processDocBlock(docBlock: DocBlockKeyValue[], document: TextDocument): ComponentAttributes {
function processDocBlock(docBlock: DocBlockKeyValue[]): ComponentAttributes {
let docBlockObj: ComponentAttributes = {};
docBlock.forEach((docElem: DocBlockKeyValue) => {
const activeKey = docElem.key;
Expand Down Expand Up @@ -324,23 +324,23 @@ function processAttributes(attributes: Attributes): ComponentAttributes {
* @param baseUri The URI from which the component path will be resolved
*/
export function componentPathToUri(dotPath: string, baseUri: Uri): Uri {
const cachedResult = cachedEntities.componentPathToUri(dotPath, baseUri);
const cachedResult: Uri = cachedEntities.componentPathToUri(dotPath, baseUri);
if (cachedResult) {
return cachedResult;
}

const normalizedPath = dotPath.replace(/\./g, path.sep) + COMPONENT_EXT;
const normalizedPath: string = dotPath.replace(/\./g, path.sep) + COMPONENT_EXT;

// relative to local directory
const baseDir = path.dirname(baseUri.fsPath);
const localPath = path.join(baseDir, normalizedPath);
const baseDir: string = path.dirname(baseUri.fsPath);
const localPath: string = path.join(baseDir, normalizedPath);
if (fs.existsSync(localPath)) {
return Uri.file(localPath);
}

// relative to web root
const root = workspace.getWorkspaceFolder(baseUri);
const rootPath = path.join(root.uri.fsPath, normalizedPath);
const root: WorkspaceFolder = workspace.getWorkspaceFolder(baseUri);
const rootPath: string = path.join(root.uri.fsPath, normalizedPath);
if (fs.existsSync(rootPath)) {
return Uri.file(rootPath);
}
Expand Down

0 comments on commit 0abbb6b

Please sign in to comment.