[ADF-3093] Added style checking tool for en.json translation file (#3428)

* [ADF-3093] Started li18nt VS Code extension

* [ADF-3093] Started work on UI style lint tool for VSCode

* [ADF-3093] Added UI style rules up to sg0006

* [ADF-3093] Added remaining style rules

* [ADF-3093] Added docs and command line tool

* [ADF-3093] Removed Microsoft notices and updated licences to Apache-2.0
This commit is contained in:
Andy Stark
2018-06-05 16:49:52 +01:00
committed by Eugenio Romano
parent 4225bf5213
commit dfc83489e2
21 changed files with 2667 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
# UI text style guide in rule form
This document lists the guidelines from the
[Alfresco UI style guide](https://docs.alfresco.com/sites/docs.alfresco.com/files/public/docs_team/u2/Alfresco-Writing-Guide.pdf)
in the form of separate rules. The list can be as a check to identify
specific types of mistake and suggest solutions. However, the main
[style guide](https://docs.alfresco.com/sites/docs.alfresco.com/files/public/docs_team/u2/Alfresco-Writing-Guide.pdf)
remains the definitive source for the guidelines and explains and develops the reasons for
style choices in greater depth.
## Rules
### SG0001: Avoid polite words ("please", "thank you")
People tend to find direct commands ("do this", "go here") rude in everyday conversation but, in UI text, the meaning is very different.
Saying "use xxx to solve problem yyy" reads as a recommendation
("if you think you have problem yyy then using xxx is a good way to solve it").
Using expressions like "please", "thank you", and "feel free" takes up space and makes the text less direct and easy to read.
### SG0002: Avoid interjections ("oops", "wow", "yeah")
It is quite common for UI text in other products to include words like this to add a bit of fun to the UI.
However, it is Alfresco's style to avoid these words to keep the UI more "serious" and professional.
### SG0003: Avoid exclamations ("!")
Exclamations generally look as though they are supposed to be funny or angry. This doesn't
fit in well with Alfresco's professional UI style.
### SG0004: Avoid unusual punctuation (";", "~", "^")
These punctuation marks are valid in English but their meaning is often subtle and they
are rarely used. The text is generally clearer without them, especially for a user who isn't
a native English speaker.
### SG0005: Don't use the ampersand ("&") as a replacement for "and"
The ampersand ("&") character is common in signs and captions but isn't normally used in
messages and body text. Use the word "and" in full.
### SG0006: Write numbers using digits instead of words
Numbers take up a lot more screen space when they are written out as words ("one hundred
and twenty three") rather than digits ("123"). Style guides differ on this issue but Alfresco's
style is to prefer digits over words when writing numbers. The exception is for numbers from
1 million upwards. For these, write digits followed by "million", "billion", etc. Something like
"10 billion" is easier to read than 10000000000.
### SG0007: Contractions ("can't", "won't" "isn't") are usually better than the equivalent phrase
Contractions such as "can't", "won't" and "isn't" are shorter and more familiar than "cannot",
"will not", "is not" and help to make the UI seem a bit friendlier. Avoid the full phrase except
in cases where you want to emphasise an instruction very strongly ("Do not change these settings
unless you are sure you need to do so").
### SG0008: Leave out trademark and copyright symbols
There is no legal requirement to use trademark and copyright symbols ("™", "©", "®") in UI
text. They will be covered in the docs if they are needed at all, so don't use them in the UI.
### SG0009: Avoid abbreviations for common phrases ("etc", "e.g.")
Abbreviations that represent English phrases ("i.e.", "etc") tend to read awkwardly and
so they should be avoided in UI text. The exception is OK, which is widely used.
However, abbreviations and acronyms for well-known products and terms (ADF, SDK)
are fine.

25
tools/i18n/cmdLi18nt.js Normal file
View File

@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var fs = require("fs");
var path = require("path");
var program = require("commander");
var SGStyleRules_1 = require("./li18nt/server/src/SGStyleRules");
program
.usage(" <source>")
.parse(process.argv);
if (program.args.length === 0) {
console.log('Error: source file "en.json" must be provided');
process.exit();
}
var text = fs.readFileSync(path.resolve(program.args[0]), 'utf8');
var lines = text.split(/\r?\n/g);
var messages = [];
lines.forEach(function (line, index) {
SGStyleRules_1.rules.forEach(function (rule) {
var newProblems = rule(line, index);
messages.push.apply(messages, newProblems);
});
});
messages.forEach(function (message) {
console.log("Line " + message.lineNum + " (" + message.startCharPos + "-" + message.endCharPos + "): " + SGStyleRules_1.sgErrorMessages[message.messageCode]);
});

33
tools/i18n/cmdLi18nt.ts Normal file
View File

@@ -0,0 +1,33 @@
import * as fs from 'fs';
import * as path from 'path';
import * as program from 'commander';
import {
SGStringProblem, sgErrorMessages, rules
} from './li18nt/server/src/SGStyleRules';
program
.usage(" <source>")
.parse(process.argv);
if (program.args.length === 0) {
console.log('Error: source file "en.json" must be provided');
process.exit();
}
let text = fs.readFileSync(path.resolve(program.args[0]), 'utf8');
let lines = text.split(/\r?\n/g);
let messages: SGStringProblem[] = [];
lines.forEach((line, index) => {
rules.forEach(rule => {
let newProblems = rule(line, index);
messages.push(...newProblems);
});
});
messages.forEach(message => {
console.log(`Line ${message.lineNum} (${message.startCharPos}-${message.endCharPos}): ${sgErrorMessages[message.messageCode]}`)
});

3
tools/i18n/li18nt/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
out
node_modules
client/server

32
tools/i18n/li18nt/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,32 @@
{
"version": "0.2.0",
// List of configurations. Add new configurations or edit existing ones.
"configurations": [
{
"name": "Launch Client",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/client"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/client/out/**/*.js"
],
"preLaunchTask": "watch:client"
},
{
"name": "Attach to Server",
"type": "node",
"request": "attach",
"port": 6009,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/client/server/**/*.js"
],
"preLaunchTask": "watch:server"
}
]
}

15
tools/i18n/li18nt/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
// set to false to hide directory from the VS Code Document Sidebar
"files.exclude": {
"client/out/**/*": false,
"client/server/**/*": false,
"client/node_modules": false,
"server/node_modules": false
},
// set this to false to include "out" folder in search results
"search.exclude": {
"out": true
},
"typescript.tsdk": "./node_modules/typescript/lib",
"typescript.tsc.autoDetect": "off"
}

79
tools/i18n/li18nt/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,79 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "compile",
"dependsOn": [
"compile:client",
"compile:server"
],
"problemMatcher": []
},
{
"label": "compile:client",
"type": "npm",
"script": "compile:client",
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": [
"$tsc"
]
},
{
"label": "compile:server",
"type": "npm",
"script": "compile:server",
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": [
"$tsc"
]
},
{
"label": "watch",
"dependsOn": [
"watch:client",
"watch:server"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "watch:client",
"type": "npm",
"script": "watch:client",
"isBackground": true,
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": [
"$tsc-watch"
]
},
{
"label": "watch:server",
"type": "npm",
"script": "watch:server",
"isBackground": true,
"group": "build",
"presentation": {
"panel": "dedicated",
"reveal": "never"
},
"problemMatcher": [
"$tsc-watch"
]
}
]
}

View File

@@ -0,0 +1,36 @@
# li18nt UI text style checker
The purpose of this tool is to provide style "lint" checking for the en.json
file, which is used as the starting point for i18n in the user interface. The
definitive guidelines can be found in the main
[style guide](https://docs.alfresco.com/sites/docs.alfresco.com/files/public/docs_team/u2/Alfresco-Writing-Guide.pdf) document. This tool implements the guidelines as a set of rules that can be
checked automatically for common errors (the [style rules](../UIStyleRules.md) file
contains a full description of the rules currently in use).
## Installing and using the VSCode extension
The VS Code extension shows the style warnings as underlined sections in the text
with corresponding notes in the Problems window, a lot like standard programming
language errors.
The extension is not available in the VS Code marketplace but you can install it
locally by copying it to the extensions folder. This can be found at the path
`$HOME/.vscode/extensions`
...on MacOSX and at
`%USERPROFILE%\.vscode\extensions`
...on Windows. Copy the `client` folder (from `alfresco-ng2-components/tools/i18n/li18nt/`)
to the extensions folder and rename it to `li18nt`. If there is no `node_modules`
folder in the new `li18nt` folder then you should also `cd` into this folder and
run `npm install`. When you restart VS Code, you should find `li18nt` listed among
the installed extensions in the extensions panel.
When active, the extension will only check the text of files named `en.json`.
Double-click an item from the Problems window in VS Code to highlight the section
of text where the issue occurs. You can find out more about why the error has
occurred and what to do about it in the [style rules](../UIStyleRules.md) file.
For the full description and explanation of all style guidelines, see the main
[style guide](https://docs.alfresco.com/sites/docs.alfresco.com/files/public/docs_team/u2/Alfresco-Writing-Guide.pdf).

View File

@@ -0,0 +1,9 @@
.vscode/**
typings/**
out/test/**
test/**
src/**
**/*.map
.gitignore
tsconfig.json
vsc-extension-quickstart.md

1795
tools/i18n/li18nt/client/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
{
"name": "li18nt",
"description": "Style checker for ADF translation files.",
"author": "Alfresco Software",
"license": "Apache-2.0",
"version": "0.0.1",
"publisher": "vscode",
"repository": {
"type": "git",
"url": "https://github.com/Alfresco/alfresco-ng2-components"
},
"engines": {
"vscode": "^1.22.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onLanguage:json"
],
"main": "./out/src/extension",
"contributes": {
"configuration": {
"type": "object",
"title": "Main configuration",
"properties": {
"li18nt.maxNumberOfProblems": {
"scope": "resource",
"type": "number",
"default": 100,
"description": "Controls the maximum number of problems produced by the server."
},
"li18nt.trace.server": {
"scope": "window",
"type": "string",
"enum": [
"off",
"messages",
"verbose"
],
"default": "off",
"description": "Traces the communication between VSCode and the language server."
}
}
}
},
"scripts": {
"vscode:prepublish": "tsc -p ./",
"compile": "tsc -p ./",
"watch": "tsc -w -p ./",
"update-vscode": "node ./node_modules/vscode/bin/install",
"postinstall": "node ./node_modules/vscode/bin/install"
},
"dependencies": {
"vscode": "^1.1.17",
"vscode-languageclient": "^4.1.3"
}
}

View File

@@ -0,0 +1,40 @@
'use strict';
import * as path from 'path';
import { workspace, ExtensionContext } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient';
export function activate(context: ExtensionContext) {
// The server is implemented in node
let serverModule = context.asAbsolutePath(path.join('server', 'server.js'));
// The debug options for the server
let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] };
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
let serverOptions: ServerOptions = {
run : { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
}
// Options to control the language client
let clientOptions: LanguageClientOptions = {
// Register the server for plain text documents
documentSelector: [{scheme: 'file', language: 'json'}],
synchronize: {
// Synchronize the setting section 'languageServerExample' to the server
configurationSection: 'li18nt',
// Notify the server about file changes to '.clientrc files contain in the workspace
fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
}
}
// Create the language client and start the client.
let disposable = new LanguageClient('li18nt', 'li18nt', serverOptions, clientOptions).start();
// Push the disposable to the context's subscriptions so that the
// client can be deactivated on extension deactivation
context.subscriptions.push(disposable);
}

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"rootDir": ".",
"outDir": "out",
"lib": [ "es2016" ],
"sourceMap": true
},
"exclude": [
"node_modules",
"server"
]
}

5
tools/i18n/li18nt/package-lock.json generated Normal file
View File

@@ -0,0 +1,5 @@
{
"name": "lsp--sample",
"version": "0.0.1",
"lockfileVersion": 1
}

View File

@@ -0,0 +1,21 @@
{
"name": "li18nt",
"description": "Style checker for ADF translation files",
"author": "Alfresco Software",
"license": "Apache-2.0",
"version": "0.0.1",
"publisher": "vscode",
"repository": {
"type": "git",
"url": "https://github.com/Alfresco/alfresco-ng2-components"
},
"scripts": {
"postinstall": "cd server && npm install && cd ../client && npm install && cd ..",
"compile": "tsc -p client/tsconfig.json && cd server && npm run installServer && cd .. && tsc -p server/tsconfig.json",
"compile:client": "tsc -p client/tsconfig.json",
"watch:client": "tsc -w -p client/tsconfig.json",
"compile:server": "cd server && npm run installServer && cd .. && tsc -p server/tsconfig.json",
"watch:server": "cd server && npm run installServer && cd .. && tsc -w -p server/tsconfig.json"
},
"devDependencies": {}
}

View File

@@ -0,0 +1,41 @@
{
"name": "lsp-sample",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"vscode-jsonrpc": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.6.2.tgz",
"integrity": "sha512-T24Jb5V48e4VgYliUXMnZ379ItbrXgOimweKaJshD84z+8q7ZOZjJan0MeDe+Ugb+uqERDVV8SBmemaGMSMugA=="
},
"vscode-languageserver": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-4.1.3.tgz",
"integrity": "sha512-D6p3q9x8QPtPLRUO5d2UKizjFYfg8zLVJqKoMpAaom8Wuhl1oKRCjeLg+Cp4mgPeCwR71wbgX2BM/jL51ni/0g==",
"requires": {
"vscode-languageserver-protocol": "3.7.2",
"vscode-uri": "1.0.3"
}
},
"vscode-languageserver-protocol": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.7.2.tgz",
"integrity": "sha512-VVJwIA/FPl/FnVtrns0FPK6TLi/ET7n1Yo6tCrm6aG7+yAVwIGWdpTmKE+nbP8wEMMbHCkIabk63IJvfz2HNRg==",
"requires": {
"vscode-jsonrpc": "3.6.2",
"vscode-languageserver-types": "3.7.2"
}
},
"vscode-languageserver-types": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.7.2.tgz",
"integrity": "sha512-L9D2RA+PDS2CiyhLQY5ZrOmyRvXyjc4Ha8s9PqS6mIgGxj00R5Xx2vLKBnAOVfrawJXYZST+2hioMks6SQVU7A=="
},
"vscode-uri": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.3.tgz",
"integrity": "sha1-Yxvb9xbcyrDmUpGo3CXCMjIIWlI="
}
}
}

View File

@@ -0,0 +1,22 @@
{
"name": "li18nt",
"description": "Style checker for ADF translation files.",
"version": "0.0.1",
"author": "Alfresco Software",
"license": "Apache-2.0",
"engines": {
"node": "*"
},
"repository": {
"type": "git",
"url": "https://github.com/Alfresco/alfresco-ng2-components"
},
"dependencies": {
"vscode-languageserver": "^4.1.2"
},
"scripts": {
"installServer": "installServerIntoExtension ../client ./package.json ./tsconfig.json",
"compile": "installServerIntoExtension ../client ./package.json ./tsconfig.json && tsc -p .",
"watch": "installServerIntoExtension ../client ./package.json ./tsconfig.json && tsc -w -p ."
}
}

View File

@@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var SGStringProblem = /** @class */ (function () {
function SGStringProblem(messageCode, lineNum, startCharPos, endCharPos) {
this.messageCode = messageCode;
this.lineNum = lineNum;
this.startCharPos = startCharPos;
this.endCharPos = endCharPos;
}
return SGStringProblem;
}());
exports.SGStringProblem = SGStringProblem;
exports.sgErrorMessages = {
'SG0001': 'Polite words are not recommended for UI text',
'SG0002': 'Avoid using interjections in UI text ("oops", "yeah", "wow")',
'SG0003': 'Avoid exclamations ("!")',
'SG0004': 'Avoid unusual punctuation marks (";", "~", "^")',
'SG0005': 'Don\'t use the ampersand ("&") as a replacement for "and"',
'SG0006': 'Write numbers using digits instead of words',
'SG0007': 'Contractions ("can\'t", "won\'t" "isn\'t") are usually better than the equivalent phrase',
'SG0008': 'Leave out trademark and copyright symbols',
'SG0009': 'Avoid abbreviations for common phrases ("etc", "e.g.")'
};
exports.rules = [
sg0001, sg0002, sg0003, sg0004, sg0005, sg0006, sg0007, sg0008, sg0009
];
function sg0001(line, lineNum) {
// Use global regex to allow repeated searching from the end of the
// previous match.
var checkWords = /\b(please|thanks|thank you|sorry)\b/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0001");
}
function sg0002(line, lineNum) {
var checkWords = /\b(whoops|oops|wow|yeah|hey|oh|aw)\b/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0002");
}
function sg0003(line, lineNum) {
var checkWords = /!+/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0003");
}
function sg0004(line, lineNum) {
var checkWords = /[;~\^]+/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0004");
}
function sg0005(line, lineNum) {
var checkWords = /&+/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0005");
}
function sg0006(line, lineNum) {
var checkWords = /\b(two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety|hundred|thousand)\b/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0006");
}
function sg0007(line, lineNum) {
var checkWords = /\b(cannot|do not|will not|have not|is not|should not|you are|we will)\b/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0007");
}
function sg0008(line, lineNum) {
var checkWords = /[\u00a9\u00ae\u2122]+/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0008");
}
function sg0009(line, lineNum) {
var checkWords = /\b(etc|e\.g\.|eg|i\.e\.|ie|n\.b\.|nb)\b/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0009");
}
function checkForRegExpMatches(re, line, lineNum, ruleName) {
var problems = [];
var matchInfo = re.exec(line);
while (matchInfo) {
problems.push(new SGStringProblem(ruleName, lineNum, matchInfo.index, matchInfo.index + matchInfo[0].length));
matchInfo = re.exec(line);
}
return problems;
}

View File

@@ -0,0 +1,108 @@
export class SGStringProblem {
constructor(
public messageCode: string,
public lineNum: number,
public startCharPos: number,
public endCharPos: number
){}
}
export let sgErrorMessages: {[index: string]: string} = {
'SG0001': 'Polite words are not recommended for UI text',
'SG0002': 'Avoid using interjections in UI text ("oops", "yeah", "wow")',
'SG0003': 'Avoid exclamations ("!")',
'SG0004': 'Avoid unusual punctuation marks (";", "~", "^")',
'SG0005': 'Don\'t use the ampersand ("&") as a replacement for "and"',
'SG0006': 'Write numbers using digits instead of words',
'SG0007': 'Contractions ("can\'t", "won\'t" "isn\'t") are usually better than the equivalent phrase',
'SG0008': 'Leave out trademark and copyright symbols',
'SG0009': 'Avoid abbreviations for common phrases ("etc", "e.g.")'
};
export type SGRule = (line: string, lineNum: number) => SGStringProblem[];
export let rules: SGRule[] = [
sg0001, sg0002, sg0003, sg0004, sg0005, sg0006, sg0007, sg0008, sg0009
];
function sg0001(line: string, lineNum: number): SGStringProblem[] {
// Use global regex to allow repeated searching from the end of the
// previous match.
let checkWords = /\b(please|thanks|thank you|sorry)\b/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0001");
}
function sg0002(line: string, lineNum: number): SGStringProblem[] {
let checkWords = /\b(whoops|oops|wow|yeah|hey|oh|aw)\b/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0002");
}
function sg0003(line: string, lineNum: number): SGStringProblem[] {
let checkWords = /!+/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0003");
}
function sg0004(line: string, lineNum: number): SGStringProblem[] {
let checkWords = /[;~\^]+/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0004");
}
function sg0005(line: string, lineNum: number): SGStringProblem[] {
let checkWords = /&+/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0005");
}
function sg0006(line: string, lineNum: number): SGStringProblem[] {
let checkWords = /\b(two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety|hundred|thousand)\b/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0006");
}
function sg0007(line: string, lineNum: number): SGStringProblem[] {
let checkWords = /\b(cannot|do not|will not|have not|is not|should not|you are|we will)\b/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0007");
}
function sg0008(line: string, lineNum: number): SGStringProblem[] {
let checkWords = /[\u00a9\u00ae\u2122]+/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0008");
}
function sg0009(line: string, lineNum: number): SGStringProblem[] {
let checkWords = /\b(etc|e\.g\.|eg|i\.e\.|ie|n\.b\.|nb)\b/gi;
return checkForRegExpMatches(checkWords, line, lineNum, "SG0009");
}
function checkForRegExpMatches(re: RegExp, line: string, lineNum: number, ruleName: string): SGStringProblem[] {
let problems: SGStringProblem[] = [];
let matchInfo = re.exec(line);
while (matchInfo) {
problems.push(
new SGStringProblem(
ruleName,
lineNum,
matchInfo.index,
matchInfo.index + matchInfo[0].length
)
);
matchInfo = re.exec(line);
}
return problems;
}

View File

@@ -0,0 +1,170 @@
'use strict';
import {
IPCMessageReader, IPCMessageWriter, createConnection, IConnection, TextDocuments, TextDocument,
Diagnostic, DiagnosticSeverity, InitializeResult, TextDocumentPositionParams, CompletionItem,
CompletionItemKind
} from 'vscode-languageserver';
import {
SGStringProblem, sgErrorMessages, rules
} from './SGStyleRules';
// Create a connection for the server. The connection uses Node's IPC as a transport
let connection: IConnection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process));
// Create a simple text document manager. The text document manager
// supports full document sync only
let documents: TextDocuments = new TextDocuments();
// Make the text document manager listen on the connection
// for open, change and close text document events
documents.listen(connection);
//let shouldSendDiagnosticRelatedInformation: boolean = false;
// After the server has started the client sends an initialize request. The server receives
// in the passed params the rootPath of the workspace plus the client capabilities.
connection.onInitialize((_params): InitializeResult => {
//shouldSendDiagnosticRelatedInformation = _params.capabilities && _params.capabilities.textDocument && _params.capabilities.textDocument.publishDiagnostics && _params.capabilities.textDocument.publishDiagnostics.relatedInformation;
return {
capabilities: {
// Tell the client that the server works in FULL text document sync mode
textDocumentSync: documents.syncKind,
// Tell the client that the server support code complete
completionProvider: {
resolveProvider: true
}
}
}
});
// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
documents.onDidChangeContent((change) => {
if (change.document.uri.endsWith('en.json')) {
validateTextDocument(change.document);
}
});
// The settings interface describe the server relevant settings part
interface Settings {
li18nt: ExampleSettings;
}
// These are the example settings we defined in the client's package.json
// file
interface ExampleSettings {
maxNumberOfProblems: number;
}
// hold the maxNumberOfProblems setting
let maxNumberOfProblems: number;
// The settings have changed. Is send on server activation
// as well.
connection.onDidChangeConfiguration((change) => {
let settings = <Settings>change.settings;
maxNumberOfProblems = settings.li18nt.maxNumberOfProblems || 100;
// Revalidate any open text documents
documents.all().forEach(validateTextDocument);
});
function validateTextDocument(textDocument: TextDocument): void {
let diagnostics: Diagnostic[] = [];
let messages: SGStringProblem[] = [];
let lines = textDocument.getText().split(/\r?\n/g);
let problems = 0;
for (var i = 0; i < lines.length && problems < maxNumberOfProblems; i++) {
let line = lines[i];
rules.forEach(rule => {
let newProblems = rule(line, i);
messages.push(...newProblems);
problems += newProblems.length;
});
}
messages.forEach(message => {
let errorMessage = sgErrorMessages[message.messageCode] || "No message available";
let fullMessageText = `${message.messageCode}: ${errorMessage}`;
let diag: Diagnostic = {
severity: DiagnosticSeverity.Warning,
range: {
start: { line: message.lineNum, character: message.startCharPos },
end: { line: message.lineNum, character: message.endCharPos }
},
message: fullMessageText,
source: 'li18nt'
}
diagnostics.push(diag);
});
// Send the computed diagnostics to VSCode.
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
}
connection.onDidChangeWatchedFiles((_change) => {
// Monitored files have change in VSCode
connection.console.log('We received an file change event');
});
// This handler provides the initial list of the completion items.
connection.onCompletion((_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
// The pass parameter contains the position of the text document in
// which code complete got requested. For the example we ignore this
// info and always provide the same completion items.
return [
{
label: 'TypeScript',
kind: CompletionItemKind.Text,
data: 1
},
{
label: 'JavaScript',
kind: CompletionItemKind.Text,
data: 2
}
]
});
// This handler resolve additional information for the item selected in
// the completion list.
connection.onCompletionResolve((item: CompletionItem): CompletionItem => {
if (item.data === 1) {
item.detail = 'TypeScript details',
item.documentation = 'TypeScript documentation'
} else if (item.data === 2) {
item.detail = 'JavaScript details',
item.documentation = 'JavaScript documentation'
}
return item;
});
/*
connection.onDidOpenTextDocument((params) => {
// A text document got opened in VSCode.
// params.uri uniquely identifies the document. For documents store on disk this is a file URI.
// params.text the initial full content of the document.
connection.console.log(`${params.textDocument.uri} opened.`);
});
connection.onDidChangeTextDocument((params) => {
// The content of a text document did change in VSCode.
// params.uri uniquely identifies the document.
// params.contentChanges describe the content changes to the document.
connection.console.log(`${params.textDocument.uri} changed: ${JSON.stringify(params.contentChanges)}`);
});
connection.onDidCloseTextDocument((params) => {
// A text document got closed in VSCode.
// params.uri uniquely identifies the document.
connection.console.log(`${params.textDocument.uri} closed.`);
});
*/
// Listen on the connection
connection.listen();

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"lib" : [ "es2016" ],
"outDir": "../client/server"
},
"exclude": [
"node_modules"
]
}